• 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.stack;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.ObjectAnimator;
22 import android.animation.PropertyValuesHolder;
23 import android.animation.ValueAnimator;
24 import android.view.View;
25 import android.view.ViewGroup;
26 import android.view.animation.Interpolator;
27 
28 import com.android.systemui.Interpolators;
29 import com.android.systemui.R;
30 import com.android.systemui.statusbar.ExpandableNotificationRow;
31 import com.android.systemui.statusbar.ExpandableView;
32 import com.android.systemui.statusbar.policy.HeadsUpManager;
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_GO_TO_FULL_SHADE = 448;
45     public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464;
46     public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220;
47     public static final int ANIMATION_DURATION_CLOSE_REMOTE_INPUT = 150;
48     public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 650;
49     public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 230;
50     public static final int ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING = 80;
51     public static final int ANIMATION_DELAY_PER_ELEMENT_MANUAL = 32;
52     public static final int ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE = 48;
53     public static final int ANIMATION_DELAY_PER_ELEMENT_DARK = 24;
54     public static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE = 2;
55     public static final int ANIMATION_DELAY_HEADS_UP = 120;
56 
57     private static final int TAG_ANIMATOR_TRANSLATION_Y = R.id.translation_y_animator_tag;
58     private static final int TAG_ANIMATOR_TRANSLATION_Z = R.id.translation_z_animator_tag;
59     private static final int TAG_ANIMATOR_ALPHA = R.id.alpha_animator_tag;
60     private static final int TAG_ANIMATOR_HEIGHT = R.id.height_animator_tag;
61     private static final int TAG_ANIMATOR_TOP_INSET = R.id.top_inset_animator_tag;
62     private static final int TAG_ANIMATOR_SHADOW_ALPHA = R.id.shadow_alpha_animator_tag;
63     private static final int TAG_END_TRANSLATION_Y = R.id.translation_y_animator_end_value_tag;
64     private static final int TAG_END_TRANSLATION_Z = R.id.translation_z_animator_end_value_tag;
65     private static final int TAG_END_ALPHA = R.id.alpha_animator_end_value_tag;
66     private static final int TAG_END_HEIGHT = R.id.height_animator_end_value_tag;
67     private static final int TAG_END_TOP_INSET = R.id.top_inset_animator_end_value_tag;
68     private static final int TAG_END_SHADOW_ALPHA = R.id.shadow_alpha_animator_end_value_tag;
69     private static final int TAG_START_TRANSLATION_Y = R.id.translation_y_animator_start_value_tag;
70     private static final int TAG_START_TRANSLATION_Z = R.id.translation_z_animator_start_value_tag;
71     private static final int TAG_START_ALPHA = R.id.alpha_animator_start_value_tag;
72     private static final int TAG_START_HEIGHT = R.id.height_animator_start_value_tag;
73     private static final int TAG_START_TOP_INSET = R.id.top_inset_animator_start_value_tag;
74     private static final int TAG_START_SHADOW_ALPHA = R.id.shadow_alpha_animator_start_value_tag;
75 
76     private final Interpolator mHeadsUpAppearInterpolator;
77     private final int mGoToFullShadeAppearingTranslation;
78     private final StackViewState mTmpState = new StackViewState();
79     public NotificationStackScrollLayout mHostLayout;
80     private ArrayList<NotificationStackScrollLayout.AnimationEvent> mNewEvents =
81             new ArrayList<>();
82     private ArrayList<View> mNewAddChildren = new ArrayList<>();
83     private HashSet<View> mHeadsUpAppearChildren = new HashSet<>();
84     private HashSet<View> mHeadsUpDisappearChildren = new HashSet<>();
85     private HashSet<Animator> mAnimatorSet = new HashSet<>();
86     private Stack<AnimatorListenerAdapter> mAnimationListenerPool = new Stack<>();
87     private AnimationFilter mAnimationFilter = new AnimationFilter();
88     private long mCurrentLength;
89     private long mCurrentAdditionalDelay;
90 
91     /** The current index for the last child which was not added in this event set. */
92     private int mCurrentLastNotAddedIndex;
93     private ValueAnimator mTopOverScrollAnimator;
94     private ValueAnimator mBottomOverScrollAnimator;
95     private int mHeadsUpAppearHeightBottom;
96     private boolean mShadeExpanded;
97     private ArrayList<View> mChildrenToClearFromOverlay = new ArrayList<>();
98 
StackStateAnimator(NotificationStackScrollLayout hostLayout)99     public StackStateAnimator(NotificationStackScrollLayout hostLayout) {
100         mHostLayout = hostLayout;
101         mGoToFullShadeAppearingTranslation =
102                 hostLayout.getContext().getResources().getDimensionPixelSize(
103                         R.dimen.go_to_full_shade_appearing_translation);
104         mHeadsUpAppearInterpolator = new HeadsUpAppearInterpolator();
105     }
106 
isRunning()107     public boolean isRunning() {
108         return !mAnimatorSet.isEmpty();
109     }
110 
startAnimationForEvents( ArrayList<NotificationStackScrollLayout.AnimationEvent> mAnimationEvents, StackScrollState finalState, long additionalDelay)111     public void startAnimationForEvents(
112             ArrayList<NotificationStackScrollLayout.AnimationEvent> mAnimationEvents,
113             StackScrollState finalState, long additionalDelay) {
114 
115         processAnimationEvents(mAnimationEvents, finalState);
116 
117         int childCount = mHostLayout.getChildCount();
118         mAnimationFilter.applyCombination(mNewEvents);
119         mCurrentAdditionalDelay = additionalDelay;
120         mCurrentLength = NotificationStackScrollLayout.AnimationEvent.combineLength(mNewEvents);
121         mCurrentLastNotAddedIndex = findLastNotAddedIndex(finalState);
122         for (int i = 0; i < childCount; i++) {
123             final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);
124 
125             StackViewState viewState = finalState.getViewStateForView(child);
126             if (viewState == null || child.getVisibility() == View.GONE
127                     || applyWithoutAnimation(child, viewState, finalState)) {
128                 continue;
129             }
130 
131             startStackAnimations(child, viewState, finalState, i, -1 /* fixedDelay */);
132         }
133         if (!isRunning()) {
134             // no child has preformed any animation, lets finish
135             onAnimationFinished();
136         }
137         mHeadsUpAppearChildren.clear();
138         mHeadsUpDisappearChildren.clear();
139         mNewEvents.clear();
140         mNewAddChildren.clear();
141     }
142 
143     /**
144      * Determines if a view should not perform an animation and applies it directly.
145      *
146      * @return true if no animation should be performed
147      */
applyWithoutAnimation(ExpandableView child, StackViewState viewState, StackScrollState finalState)148     private boolean applyWithoutAnimation(ExpandableView child, StackViewState viewState,
149             StackScrollState finalState) {
150         if (mShadeExpanded) {
151             return false;
152         }
153         if (getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y) != null) {
154             // A Y translation animation is running
155             return false;
156         }
157         if (mHeadsUpDisappearChildren.contains(child) || mHeadsUpAppearChildren.contains(child)) {
158             // This is a heads up animation
159             return false;
160         }
161         if (NotificationStackScrollLayout.isPinnedHeadsUp(child)) {
162             // This is another headsUp which might move. Let's animate!
163             return false;
164         }
165         finalState.applyState(child, viewState);
166         return true;
167     }
168 
findLastNotAddedIndex(StackScrollState finalState)169     private int findLastNotAddedIndex(StackScrollState finalState) {
170         int childCount = mHostLayout.getChildCount();
171         for (int i = childCount - 1; i >= 0; i--) {
172             final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);
173 
174             StackViewState viewState = finalState.getViewStateForView(child);
175             if (viewState == null || child.getVisibility() == View.GONE) {
176                 continue;
177             }
178             if (!mNewAddChildren.contains(child)) {
179                 return viewState.notGoneIndex;
180             }
181         }
182         return -1;
183     }
184 
185 
186     /**
187      * Start an animation to the given  {@link StackViewState}.
188      *
189      * @param child the child to start the animation on
190      * @param viewState the {@link StackViewState} of the view to animate to
191      * @param finalState the final state after the animation
192      * @param i the index of the view; only relevant if the view is the speed bump and is
193      *          ignored otherwise
194      * @param fixedDelay a fixed delay if desired or -1 if the delay should be calculated
195      */
startStackAnimations(final ExpandableView child, StackViewState viewState, StackScrollState finalState, int i, long fixedDelay)196     public void startStackAnimations(final ExpandableView child, StackViewState viewState,
197             StackScrollState finalState, int i, long fixedDelay) {
198         boolean wasAdded = mNewAddChildren.contains(child);
199         long duration = mCurrentLength;
200         if (wasAdded && mAnimationFilter.hasGoToFullShadeEvent) {
201             child.setTranslationY(child.getTranslationY() + mGoToFullShadeAppearingTranslation);
202             float longerDurationFactor = viewState.notGoneIndex - mCurrentLastNotAddedIndex;
203             longerDurationFactor = (float) Math.pow(longerDurationFactor, 0.7f);
204             duration = ANIMATION_DURATION_APPEAR_DISAPPEAR + 50 +
205                     (long) (100 * longerDurationFactor);
206         }
207         boolean yTranslationChanging = child.getTranslationY() != viewState.yTranslation;
208         boolean zTranslationChanging = child.getTranslationZ() != viewState.zTranslation;
209         boolean alphaChanging = viewState.alpha != child.getAlpha();
210         boolean heightChanging = viewState.height != child.getActualHeight();
211         boolean shadowAlphaChanging = viewState.shadowAlpha != child.getShadowAlpha();
212         boolean darkChanging = viewState.dark != child.isDark();
213         boolean topInsetChanging = viewState.clipTopAmount != child.getClipTopAmount();
214         boolean hasDelays = mAnimationFilter.hasDelays;
215         boolean isDelayRelevant = yTranslationChanging || zTranslationChanging || alphaChanging
216                 || heightChanging || topInsetChanging || darkChanging || shadowAlphaChanging;
217         long delay = 0;
218         if (fixedDelay != -1) {
219             delay = fixedDelay;
220         } else if (hasDelays && isDelayRelevant || wasAdded) {
221             delay = mCurrentAdditionalDelay + calculateChildAnimationDelay(viewState, finalState);
222         }
223 
224         startViewAnimations(child, viewState, delay, duration);
225 
226         // start height animation
227         if (heightChanging) {
228             startHeightAnimation(child, viewState, duration, delay);
229         }  else {
230             abortAnimation(child, TAG_ANIMATOR_HEIGHT);
231         }
232 
233         // start shadow alpha animation
234         if (shadowAlphaChanging) {
235             startShadowAlphaAnimation(child, viewState, duration, delay);
236         } else {
237             abortAnimation(child, TAG_ANIMATOR_SHADOW_ALPHA);
238         }
239 
240         // start top inset animation
241         if (topInsetChanging) {
242             startInsetAnimation(child, viewState, duration, delay);
243         } else {
244             abortAnimation(child, TAG_ANIMATOR_TOP_INSET);
245         }
246 
247         // start dimmed animation
248         child.setDimmed(viewState.dimmed, mAnimationFilter.animateDimmed);
249 
250         // apply speed bump state
251         child.setBelowSpeedBump(viewState.belowSpeedBump);
252 
253         // start hiding sensitive animation
254         child.setHideSensitive(viewState.hideSensitive, mAnimationFilter.animateHideSensitive,
255                 delay, duration);
256 
257         // start dark animation
258         child.setDark(viewState.dark, mAnimationFilter.animateDark, delay);
259 
260         if (wasAdded) {
261             child.performAddAnimation(delay, mCurrentLength);
262         }
263         if (child instanceof ExpandableNotificationRow) {
264             ExpandableNotificationRow row = (ExpandableNotificationRow) child;
265             row.startChildAnimation(finalState, this, delay, duration);
266         }
267     }
268 
269     /**
270      * Start an animation to a new {@link ViewState}.
271      *
272      * @param child the child to start the animation on
273      * @param viewState the  {@link StackViewState} of the view to animate to
274      * @param delay a fixed delay
275      * @param duration the duration of the animation
276      */
startViewAnimations(View child, ViewState viewState, long delay, long duration)277     public void startViewAnimations(View child, ViewState viewState, long delay, long duration) {
278         boolean wasVisible = child.getVisibility() == View.VISIBLE;
279         final float alpha = viewState.alpha;
280         if (!wasVisible && (alpha != 0 || child.getAlpha() != 0)
281                 && !viewState.gone && !viewState.hidden) {
282             child.setVisibility(View.VISIBLE);
283         }
284         boolean yTranslationChanging = child.getTranslationY() != viewState.yTranslation;
285         boolean zTranslationChanging = child.getTranslationZ() != viewState.zTranslation;
286         float childAlpha = child.getAlpha();
287         boolean alphaChanging = viewState.alpha != childAlpha;
288         if (child instanceof ExpandableView) {
289             // We don't want views to change visibility when they are animating to GONE
290             alphaChanging &= !((ExpandableView) child).willBeGone();
291         }
292 
293         // start translationY animation
294         if (yTranslationChanging) {
295             startYTranslationAnimation(child, viewState, duration, delay);
296         } else {
297             abortAnimation(child, TAG_ANIMATOR_TRANSLATION_Y);
298         }
299 
300         // start translationZ animation
301         if (zTranslationChanging) {
302             startZTranslationAnimation(child, viewState, duration, delay);
303         } else {
304             abortAnimation(child, TAG_ANIMATOR_TRANSLATION_Z);
305         }
306 
307         // start alpha animation
308         if (alphaChanging && child.getTranslationX() == 0) {
309             startAlphaAnimation(child, viewState, duration, delay);
310         }  else {
311             abortAnimation(child, TAG_ANIMATOR_ALPHA);
312         }
313     }
314 
abortAnimation(View child, int animatorTag)315     private void abortAnimation(View child, int animatorTag) {
316         Animator previousAnimator = getChildTag(child, animatorTag);
317         if (previousAnimator != null) {
318             previousAnimator.cancel();
319         }
320     }
321 
calculateChildAnimationDelay(StackViewState viewState, StackScrollState finalState)322     private long calculateChildAnimationDelay(StackViewState viewState,
323             StackScrollState finalState) {
324         if (mAnimationFilter.hasDarkEvent) {
325             return calculateDelayDark(viewState);
326         }
327         if (mAnimationFilter.hasGoToFullShadeEvent) {
328             return calculateDelayGoToFullShade(viewState);
329         }
330         if (mAnimationFilter.hasHeadsUpDisappearClickEvent) {
331             return ANIMATION_DELAY_HEADS_UP;
332         }
333         long minDelay = 0;
334         for (NotificationStackScrollLayout.AnimationEvent event : mNewEvents) {
335             long delayPerElement = ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING;
336             switch (event.animationType) {
337                 case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD: {
338                     int ownIndex = viewState.notGoneIndex;
339                     int changingIndex = finalState
340                             .getViewStateForView(event.changingView).notGoneIndex;
341                     int difference = Math.abs(ownIndex - changingIndex);
342                     difference = Math.max(0, Math.min(DELAY_EFFECT_MAX_INDEX_DIFFERENCE,
343                             difference - 1));
344                     long delay = (DELAY_EFFECT_MAX_INDEX_DIFFERENCE - difference) * delayPerElement;
345                     minDelay = Math.max(delay, minDelay);
346                     break;
347                 }
348                 case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT:
349                     delayPerElement = ANIMATION_DELAY_PER_ELEMENT_MANUAL;
350                 case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE: {
351                     int ownIndex = viewState.notGoneIndex;
352                     boolean noNextView = event.viewAfterChangingView == null;
353                     View viewAfterChangingView = noNextView
354                             ? mHostLayout.getLastChildNotGone()
355                             : event.viewAfterChangingView;
356 
357                     int nextIndex = finalState
358                             .getViewStateForView(viewAfterChangingView).notGoneIndex;
359                     if (ownIndex >= nextIndex) {
360                         // we only have the view afterwards
361                         ownIndex++;
362                     }
363                     int difference = Math.abs(ownIndex - nextIndex);
364                     difference = Math.max(0, Math.min(DELAY_EFFECT_MAX_INDEX_DIFFERENCE,
365                             difference - 1));
366                     long delay = difference * delayPerElement;
367                     minDelay = Math.max(delay, minDelay);
368                     break;
369                 }
370                 default:
371                     break;
372             }
373         }
374         return minDelay;
375     }
376 
calculateDelayDark(StackViewState viewState)377     private long calculateDelayDark(StackViewState viewState) {
378         int referenceIndex;
379         if (mAnimationFilter.darkAnimationOriginIndex ==
380                 NotificationStackScrollLayout.AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE) {
381             referenceIndex = 0;
382         } else if (mAnimationFilter.darkAnimationOriginIndex ==
383                 NotificationStackScrollLayout.AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_BELOW) {
384             referenceIndex = mHostLayout.getNotGoneChildCount() - 1;
385         } else {
386             referenceIndex = mAnimationFilter.darkAnimationOriginIndex;
387         }
388         return Math.abs(referenceIndex - viewState.notGoneIndex) * ANIMATION_DELAY_PER_ELEMENT_DARK;
389     }
390 
calculateDelayGoToFullShade(StackViewState viewState)391     private long calculateDelayGoToFullShade(StackViewState viewState) {
392         float index = viewState.notGoneIndex;
393         index = (float) Math.pow(index, 0.7f);
394         return (long) (index * ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE);
395     }
396 
startShadowAlphaAnimation(final ExpandableView child, StackViewState viewState, long duration, long delay)397     private void startShadowAlphaAnimation(final ExpandableView child,
398             StackViewState viewState, long duration, long delay) {
399         Float previousStartValue = getChildTag(child, TAG_START_SHADOW_ALPHA);
400         Float previousEndValue = getChildTag(child, TAG_END_SHADOW_ALPHA);
401         float newEndValue = viewState.shadowAlpha;
402         if (previousEndValue != null && previousEndValue == newEndValue) {
403             return;
404         }
405         ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_SHADOW_ALPHA);
406         if (!mAnimationFilter.animateShadowAlpha) {
407             // just a local update was performed
408             if (previousAnimator != null) {
409                 // we need to increase all animation keyframes of the previous animator by the
410                 // relative change to the end value
411                 PropertyValuesHolder[] values = previousAnimator.getValues();
412                 float relativeDiff = newEndValue - previousEndValue;
413                 float newStartValue = previousStartValue + relativeDiff;
414                 values[0].setFloatValues(newStartValue, newEndValue);
415                 child.setTag(TAG_START_SHADOW_ALPHA, newStartValue);
416                 child.setTag(TAG_END_SHADOW_ALPHA, newEndValue);
417                 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
418                 return;
419             } else {
420                 // no new animation needed, let's just apply the value
421                 child.setShadowAlpha(newEndValue);
422                 return;
423             }
424         }
425 
426         ValueAnimator animator = ValueAnimator.ofFloat(child.getShadowAlpha(), newEndValue);
427         animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
428             @Override
429             public void onAnimationUpdate(ValueAnimator animation) {
430                 child.setShadowAlpha((float) animation.getAnimatedValue());
431             }
432         });
433         animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
434         long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
435         animator.setDuration(newDuration);
436         if (delay > 0 && (previousAnimator == null
437                 || previousAnimator.getAnimatedFraction() == 0)) {
438             animator.setStartDelay(delay);
439         }
440         animator.addListener(getGlobalAnimationFinishedListener());
441         // remove the tag when the animation is finished
442         animator.addListener(new AnimatorListenerAdapter() {
443             @Override
444             public void onAnimationEnd(Animator animation) {
445                 child.setTag(TAG_ANIMATOR_SHADOW_ALPHA, null);
446                 child.setTag(TAG_START_SHADOW_ALPHA, null);
447                 child.setTag(TAG_END_SHADOW_ALPHA, null);
448             }
449         });
450         startAnimator(animator);
451         child.setTag(TAG_ANIMATOR_SHADOW_ALPHA, animator);
452         child.setTag(TAG_START_SHADOW_ALPHA, child.getShadowAlpha());
453         child.setTag(TAG_END_SHADOW_ALPHA, newEndValue);
454     }
455 
startHeightAnimation(final ExpandableView child, StackViewState viewState, long duration, long delay)456     private void startHeightAnimation(final ExpandableView child,
457             StackViewState viewState, long duration, long delay) {
458         Integer previousStartValue = getChildTag(child, TAG_START_HEIGHT);
459         Integer previousEndValue = getChildTag(child, TAG_END_HEIGHT);
460         int newEndValue = viewState.height;
461         if (previousEndValue != null && previousEndValue == newEndValue) {
462             return;
463         }
464         ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_HEIGHT);
465         if (!mAnimationFilter.animateHeight) {
466             // just a local update was performed
467             if (previousAnimator != null) {
468                 // we need to increase all animation keyframes of the previous animator by the
469                 // relative change to the end value
470                 PropertyValuesHolder[] values = previousAnimator.getValues();
471                 int relativeDiff = newEndValue - previousEndValue;
472                 int newStartValue = previousStartValue + relativeDiff;
473                 values[0].setIntValues(newStartValue, newEndValue);
474                 child.setTag(TAG_START_HEIGHT, newStartValue);
475                 child.setTag(TAG_END_HEIGHT, newEndValue);
476                 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
477                 return;
478             } else {
479                 // no new animation needed, let's just apply the value
480                 child.setActualHeight(newEndValue, false);
481                 return;
482             }
483         }
484 
485         ValueAnimator animator = ValueAnimator.ofInt(child.getActualHeight(), newEndValue);
486         animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
487             @Override
488             public void onAnimationUpdate(ValueAnimator animation) {
489                 child.setActualHeight((int) animation.getAnimatedValue(),
490                         false /* notifyListeners */);
491             }
492         });
493         animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
494         long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
495         animator.setDuration(newDuration);
496         if (delay > 0 && (previousAnimator == null
497                 || previousAnimator.getAnimatedFraction() == 0)) {
498             animator.setStartDelay(delay);
499         }
500         animator.addListener(getGlobalAnimationFinishedListener());
501         // remove the tag when the animation is finished
502         animator.addListener(new AnimatorListenerAdapter() {
503             boolean mWasCancelled;
504 
505             @Override
506             public void onAnimationEnd(Animator animation) {
507                 child.setTag(TAG_ANIMATOR_HEIGHT, null);
508                 child.setTag(TAG_START_HEIGHT, null);
509                 child.setTag(TAG_END_HEIGHT, null);
510                 child.setActualHeightAnimating(false);
511                 if (!mWasCancelled && child instanceof ExpandableNotificationRow) {
512                     ((ExpandableNotificationRow) child).setGroupExpansionChanging(
513                             false /* isExpansionChanging */);
514                 }
515             }
516 
517             @Override
518             public void onAnimationStart(Animator animation) {
519                 mWasCancelled = false;
520             }
521 
522             @Override
523             public void onAnimationCancel(Animator animation) {
524                 mWasCancelled = true;
525             }
526         });
527         startAnimator(animator);
528         child.setTag(TAG_ANIMATOR_HEIGHT, animator);
529         child.setTag(TAG_START_HEIGHT, child.getActualHeight());
530         child.setTag(TAG_END_HEIGHT, newEndValue);
531         child.setActualHeightAnimating(true);
532     }
533 
startInsetAnimation(final ExpandableView child, StackViewState viewState, long duration, long delay)534     private void startInsetAnimation(final ExpandableView child,
535             StackViewState viewState, long duration, long delay) {
536         Integer previousStartValue = getChildTag(child, TAG_START_TOP_INSET);
537         Integer previousEndValue = getChildTag(child, TAG_END_TOP_INSET);
538         int newEndValue = viewState.clipTopAmount;
539         if (previousEndValue != null && previousEndValue == newEndValue) {
540             return;
541         }
542         ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TOP_INSET);
543         if (!mAnimationFilter.animateTopInset) {
544             // just a local update was performed
545             if (previousAnimator != null) {
546                 // we need to increase all animation keyframes of the previous animator by the
547                 // relative change to the end value
548                 PropertyValuesHolder[] values = previousAnimator.getValues();
549                 int relativeDiff = newEndValue - previousEndValue;
550                 int newStartValue = previousStartValue + relativeDiff;
551                 values[0].setIntValues(newStartValue, newEndValue);
552                 child.setTag(TAG_START_TOP_INSET, newStartValue);
553                 child.setTag(TAG_END_TOP_INSET, newEndValue);
554                 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
555                 return;
556             } else {
557                 // no new animation needed, let's just apply the value
558                 child.setClipTopAmount(newEndValue);
559                 return;
560             }
561         }
562 
563         ValueAnimator animator = ValueAnimator.ofInt(child.getClipTopAmount(), newEndValue);
564         animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
565             @Override
566             public void onAnimationUpdate(ValueAnimator animation) {
567                 child.setClipTopAmount((int) animation.getAnimatedValue());
568             }
569         });
570         animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
571         long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
572         animator.setDuration(newDuration);
573         if (delay > 0 && (previousAnimator == null
574                 || previousAnimator.getAnimatedFraction() == 0)) {
575             animator.setStartDelay(delay);
576         }
577         animator.addListener(getGlobalAnimationFinishedListener());
578         // remove the tag when the animation is finished
579         animator.addListener(new AnimatorListenerAdapter() {
580             @Override
581             public void onAnimationEnd(Animator animation) {
582                 child.setTag(TAG_ANIMATOR_TOP_INSET, null);
583                 child.setTag(TAG_START_TOP_INSET, null);
584                 child.setTag(TAG_END_TOP_INSET, null);
585             }
586         });
587         startAnimator(animator);
588         child.setTag(TAG_ANIMATOR_TOP_INSET, animator);
589         child.setTag(TAG_START_TOP_INSET, child.getClipTopAmount());
590         child.setTag(TAG_END_TOP_INSET, newEndValue);
591     }
592 
startAlphaAnimation(final View child, final ViewState viewState, long duration, long delay)593     private void startAlphaAnimation(final View child,
594             final ViewState viewState, long duration, long delay) {
595         Float previousStartValue = getChildTag(child,TAG_START_ALPHA);
596         Float previousEndValue = getChildTag(child,TAG_END_ALPHA);
597         final float newEndValue = viewState.alpha;
598         if (previousEndValue != null && previousEndValue == newEndValue) {
599             return;
600         }
601         ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_ALPHA);
602         if (!mAnimationFilter.animateAlpha) {
603             // just a local update was performed
604             if (previousAnimator != null) {
605                 // we need to increase all animation keyframes of the previous animator by the
606                 // relative change to the end value
607                 PropertyValuesHolder[] values = previousAnimator.getValues();
608                 float relativeDiff = newEndValue - previousEndValue;
609                 float newStartValue = previousStartValue + relativeDiff;
610                 values[0].setFloatValues(newStartValue, newEndValue);
611                 child.setTag(TAG_START_ALPHA, newStartValue);
612                 child.setTag(TAG_END_ALPHA, newEndValue);
613                 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
614                 return;
615             } else {
616                 // no new animation needed, let's just apply the value
617                 child.setAlpha(newEndValue);
618                 if (newEndValue == 0) {
619                     child.setVisibility(View.INVISIBLE);
620                 }
621             }
622         }
623 
624         ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.ALPHA,
625                 child.getAlpha(), newEndValue);
626         animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
627         // Handle layer type
628         child.setLayerType(View.LAYER_TYPE_HARDWARE, null);
629         animator.addListener(new AnimatorListenerAdapter() {
630             public boolean mWasCancelled;
631 
632             @Override
633             public void onAnimationEnd(Animator animation) {
634                 child.setLayerType(View.LAYER_TYPE_NONE, null);
635                 if (newEndValue == 0 && !mWasCancelled) {
636                     child.setVisibility(View.INVISIBLE);
637                 }
638                 // remove the tag when the animation is finished
639                 child.setTag(TAG_ANIMATOR_ALPHA, null);
640                 child.setTag(TAG_START_ALPHA, null);
641                 child.setTag(TAG_END_ALPHA, null);
642             }
643 
644             @Override
645             public void onAnimationCancel(Animator animation) {
646                 mWasCancelled = true;
647             }
648 
649             @Override
650             public void onAnimationStart(Animator animation) {
651                 mWasCancelled = false;
652             }
653         });
654         long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
655         animator.setDuration(newDuration);
656         if (delay > 0 && (previousAnimator == null
657                 || previousAnimator.getAnimatedFraction() == 0)) {
658             animator.setStartDelay(delay);
659         }
660         animator.addListener(getGlobalAnimationFinishedListener());
661 
662         startAnimator(animator);
663         child.setTag(TAG_ANIMATOR_ALPHA, animator);
664         child.setTag(TAG_START_ALPHA, child.getAlpha());
665         child.setTag(TAG_END_ALPHA, newEndValue);
666     }
667 
startZTranslationAnimation(final View child, final ViewState viewState, long duration, long delay)668     private void startZTranslationAnimation(final View child,
669             final ViewState viewState, long duration, long delay) {
670         Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Z);
671         Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Z);
672         float newEndValue = viewState.zTranslation;
673         if (previousEndValue != null && previousEndValue == newEndValue) {
674             return;
675         }
676         ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Z);
677         if (!mAnimationFilter.animateZ) {
678             // just a local update was performed
679             if (previousAnimator != null) {
680                 // we need to increase all animation keyframes of the previous animator by the
681                 // relative change to the end value
682                 PropertyValuesHolder[] values = previousAnimator.getValues();
683                 float relativeDiff = newEndValue - previousEndValue;
684                 float newStartValue = previousStartValue + relativeDiff;
685                 values[0].setFloatValues(newStartValue, newEndValue);
686                 child.setTag(TAG_START_TRANSLATION_Z, newStartValue);
687                 child.setTag(TAG_END_TRANSLATION_Z, newEndValue);
688                 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
689                 return;
690             } else {
691                 // no new animation needed, let's just apply the value
692                 child.setTranslationZ(newEndValue);
693             }
694         }
695 
696         ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Z,
697                 child.getTranslationZ(), newEndValue);
698         animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
699         long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
700         animator.setDuration(newDuration);
701         if (delay > 0 && (previousAnimator == null
702                 || previousAnimator.getAnimatedFraction() == 0)) {
703             animator.setStartDelay(delay);
704         }
705         animator.addListener(getGlobalAnimationFinishedListener());
706         // remove the tag when the animation is finished
707         animator.addListener(new AnimatorListenerAdapter() {
708             @Override
709             public void onAnimationEnd(Animator animation) {
710                 child.setTag(TAG_ANIMATOR_TRANSLATION_Z, null);
711                 child.setTag(TAG_START_TRANSLATION_Z, null);
712                 child.setTag(TAG_END_TRANSLATION_Z, null);
713             }
714         });
715         startAnimator(animator);
716         child.setTag(TAG_ANIMATOR_TRANSLATION_Z, animator);
717         child.setTag(TAG_START_TRANSLATION_Z, child.getTranslationZ());
718         child.setTag(TAG_END_TRANSLATION_Z, newEndValue);
719     }
720 
startYTranslationAnimation(final View child, ViewState viewState, long duration, long delay)721     private void startYTranslationAnimation(final View child,
722             ViewState viewState, long duration, long delay) {
723         Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Y);
724         Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Y);
725         float newEndValue = viewState.yTranslation;
726         if (previousEndValue != null && previousEndValue == newEndValue) {
727             return;
728         }
729         ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y);
730         if (!mAnimationFilter.animateY) {
731             // just a local update was performed
732             if (previousAnimator != null) {
733                 // we need to increase all animation keyframes of the previous animator by the
734                 // relative change to the end value
735                 PropertyValuesHolder[] values = previousAnimator.getValues();
736                 float relativeDiff = newEndValue - previousEndValue;
737                 float newStartValue = previousStartValue + relativeDiff;
738                 values[0].setFloatValues(newStartValue, newEndValue);
739                 child.setTag(TAG_START_TRANSLATION_Y, newStartValue);
740                 child.setTag(TAG_END_TRANSLATION_Y, newEndValue);
741                 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
742                 return;
743             } else {
744                 // no new animation needed, let's just apply the value
745                 child.setTranslationY(newEndValue);
746                 return;
747             }
748         }
749 
750         ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Y,
751                 child.getTranslationY(), newEndValue);
752         Interpolator interpolator = mHeadsUpAppearChildren.contains(child) ?
753                 mHeadsUpAppearInterpolator :Interpolators.FAST_OUT_SLOW_IN;
754         animator.setInterpolator(interpolator);
755         long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
756         animator.setDuration(newDuration);
757         if (delay > 0 && (previousAnimator == null
758                 || previousAnimator.getAnimatedFraction() == 0)) {
759             animator.setStartDelay(delay);
760         }
761         animator.addListener(getGlobalAnimationFinishedListener());
762         final boolean isHeadsUpDisappear = mHeadsUpDisappearChildren.contains(child);
763         // remove the tag when the animation is finished
764         animator.addListener(new AnimatorListenerAdapter() {
765             @Override
766             public void onAnimationEnd(Animator animation) {
767                 HeadsUpManager.setIsClickedNotification(child, false);
768                 child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null);
769                 child.setTag(TAG_START_TRANSLATION_Y, null);
770                 child.setTag(TAG_END_TRANSLATION_Y, null);
771                 if (isHeadsUpDisappear) {
772                     ((ExpandableNotificationRow) child).setHeadsupDisappearRunning(false);
773                 }
774             }
775         });
776         startAnimator(animator);
777         child.setTag(TAG_ANIMATOR_TRANSLATION_Y, animator);
778         child.setTag(TAG_START_TRANSLATION_Y, child.getTranslationY());
779         child.setTag(TAG_END_TRANSLATION_Y, newEndValue);
780     }
781 
startAnimator(ValueAnimator animator)782     private void startAnimator(ValueAnimator animator) {
783         mAnimatorSet.add(animator);
784         animator.start();
785     }
786 
787     /**
788      * @return an adapter which ensures that onAnimationFinished is called once no animation is
789      *         running anymore
790      */
getGlobalAnimationFinishedListener()791     private AnimatorListenerAdapter getGlobalAnimationFinishedListener() {
792         if (!mAnimationListenerPool.empty()) {
793             return mAnimationListenerPool.pop();
794         }
795 
796         // We need to create a new one, no reusable ones found
797         return new AnimatorListenerAdapter() {
798             private boolean mWasCancelled;
799 
800             @Override
801             public void onAnimationEnd(Animator animation) {
802                 mAnimatorSet.remove(animation);
803                 if (mAnimatorSet.isEmpty() && !mWasCancelled) {
804                     onAnimationFinished();
805                 }
806                 mAnimationListenerPool.push(this);
807             }
808 
809             @Override
810             public void onAnimationCancel(Animator animation) {
811                 mWasCancelled = true;
812             }
813 
814             @Override
815             public void onAnimationStart(Animator animation) {
816                 mWasCancelled = false;
817             }
818         };
819     }
820 
getChildTag(View child, int tag)821     public static <T> T getChildTag(View child, int tag) {
822         return (T) child.getTag(tag);
823     }
824 
825     /**
826      * Cancel the previous animator and get the duration of the new animation.
827      *
828      * @param duration the new duration
829      * @param previousAnimator the animator which was running before
830      * @return the new duration
831      */
cancelAnimatorAndGetNewDuration(long duration, ValueAnimator previousAnimator)832     private long cancelAnimatorAndGetNewDuration(long duration, ValueAnimator previousAnimator) {
833         long newDuration = duration;
834         if (previousAnimator != null) {
835             // We take either the desired length of the new animation or the remaining time of
836             // the previous animator, whichever is longer.
837             newDuration = Math.max(previousAnimator.getDuration()
838                     - previousAnimator.getCurrentPlayTime(), newDuration);
839             previousAnimator.cancel();
840         }
841         return newDuration;
842     }
843 
onAnimationFinished()844     private void onAnimationFinished() {
845         mHostLayout.onChildAnimationFinished();
846         for (View v : mChildrenToClearFromOverlay) {
847             removeFromOverlay(v);
848         }
849         mChildrenToClearFromOverlay.clear();
850     }
851 
852     /**
853      * Process the animationEvents for a new animation
854      *
855      * @param animationEvents the animation events for the animation to perform
856      * @param finalState the final state to animate to
857      */
processAnimationEvents( ArrayList<NotificationStackScrollLayout.AnimationEvent> animationEvents, StackScrollState finalState)858     private void processAnimationEvents(
859             ArrayList<NotificationStackScrollLayout.AnimationEvent> animationEvents,
860             StackScrollState finalState) {
861         for (NotificationStackScrollLayout.AnimationEvent event : animationEvents) {
862             final ExpandableView changingView = (ExpandableView) event.changingView;
863             if (event.animationType ==
864                     NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD) {
865 
866                 // This item is added, initialize it's properties.
867                 StackViewState viewState = finalState
868                         .getViewStateForView(changingView);
869                 if (viewState == null) {
870                     // The position for this child was never generated, let's continue.
871                     continue;
872                 }
873                 finalState.applyState(changingView, viewState);
874                 mNewAddChildren.add(changingView);
875 
876             } else if (event.animationType ==
877                     NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE) {
878                 if (changingView.getVisibility() == View.GONE) {
879                     removeFromOverlay(changingView);
880                     continue;
881                 }
882 
883                 // Find the amount to translate up. This is needed in order to understand the
884                 // direction of the remove animation (either downwards or upwards)
885                 StackViewState viewState = finalState
886                         .getViewStateForView(event.viewAfterChangingView);
887                 int actualHeight = changingView.getActualHeight();
888                 // upwards by default
889                 float translationDirection = -1.0f;
890                 if (viewState != null) {
891                     // there was a view after this one, Approximate the distance the next child
892                     // travelled
893                     translationDirection = ((viewState.yTranslation
894                             - (changingView.getTranslationY() + actualHeight / 2.0f)) * 2 /
895                             actualHeight);
896                     translationDirection = Math.max(Math.min(translationDirection, 1.0f),-1.0f);
897 
898                 }
899                 changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR,
900                         translationDirection, new Runnable() {
901                     @Override
902                     public void run() {
903                         // remove the temporary overlay
904                         removeFromOverlay(changingView);
905                     }
906                 });
907             } else if (event.animationType ==
908                 NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) {
909                 // A race condition can trigger the view to be added to the overlay even though
910                 // it was fully swiped out. So let's remove it
911                 mHostLayout.getOverlay().remove(changingView);
912                 if (Math.abs(changingView.getTranslation()) == changingView.getWidth()
913                         && changingView.getTransientContainer() != null) {
914                     changingView.getTransientContainer().removeTransientView(changingView);
915                 }
916             } else if (event.animationType == NotificationStackScrollLayout
917                     .AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED) {
918                 ExpandableNotificationRow row = (ExpandableNotificationRow) event.changingView;
919                 row.prepareExpansionChanged(finalState);
920             } else if (event.animationType == NotificationStackScrollLayout
921                     .AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR) {
922                 // This item is added, initialize it's properties.
923                 StackViewState viewState = finalState.getViewStateForView(changingView);
924                 mTmpState.copyFrom(viewState);
925                 if (event.headsUpFromBottom) {
926                     mTmpState.yTranslation = mHeadsUpAppearHeightBottom;
927                 } else {
928                     mTmpState.yTranslation = -mTmpState.height;
929                 }
930                 mHeadsUpAppearChildren.add(changingView);
931                 finalState.applyState(changingView, mTmpState);
932             } else if (event.animationType == NotificationStackScrollLayout
933                             .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR ||
934                     event.animationType == NotificationStackScrollLayout
935                             .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK) {
936                 mHeadsUpDisappearChildren.add(changingView);
937                 if (changingView.getParent() == null) {
938                     // This notification was actually removed, so we need to add it to the overlay
939                     mHostLayout.getOverlay().add(changingView);
940                     mTmpState.initFrom(changingView);
941                     mTmpState.yTranslation = -changingView.getActualHeight();
942                     // We temporarily enable Y animations, the real filter will be combined
943                     // afterwards anyway
944                     mAnimationFilter.animateY = true;
945                     startViewAnimations(changingView, mTmpState,
946                             event.animationType == NotificationStackScrollLayout
947                                     .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
948                                             ? ANIMATION_DELAY_HEADS_UP
949                                             : 0,
950                             ANIMATION_DURATION_HEADS_UP_DISAPPEAR);
951                     mChildrenToClearFromOverlay.add(changingView);
952                 }
953             }
954             mNewEvents.add(event);
955         }
956     }
957 
removeFromOverlay(View changingView)958     public static void removeFromOverlay(View changingView) {
959         ViewGroup parent = (ViewGroup) changingView.getParent();
960         if (parent != null) {
961             parent.removeView(changingView);
962         }
963     }
964 
animateOverScrollToAmount(float targetAmount, final boolean onTop, final boolean isRubberbanded)965     public void animateOverScrollToAmount(float targetAmount, final boolean onTop,
966             final boolean isRubberbanded) {
967         final float startOverScrollAmount = mHostLayout.getCurrentOverScrollAmount(onTop);
968         if (targetAmount == startOverScrollAmount) {
969             return;
970         }
971         cancelOverScrollAnimators(onTop);
972         ValueAnimator overScrollAnimator = ValueAnimator.ofFloat(startOverScrollAmount,
973                 targetAmount);
974         overScrollAnimator.setDuration(ANIMATION_DURATION_STANDARD);
975         overScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
976             @Override
977             public void onAnimationUpdate(ValueAnimator animation) {
978                 float currentOverScroll = (float) animation.getAnimatedValue();
979                 mHostLayout.setOverScrollAmount(
980                         currentOverScroll, onTop, false /* animate */, false /* cancelAnimators */,
981                         isRubberbanded);
982             }
983         });
984         overScrollAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
985         overScrollAnimator.addListener(new AnimatorListenerAdapter() {
986             @Override
987             public void onAnimationEnd(Animator animation) {
988                 if (onTop) {
989                     mTopOverScrollAnimator = null;
990                 } else {
991                     mBottomOverScrollAnimator = null;
992                 }
993             }
994         });
995         overScrollAnimator.start();
996         if (onTop) {
997             mTopOverScrollAnimator = overScrollAnimator;
998         } else {
999             mBottomOverScrollAnimator = overScrollAnimator;
1000         }
1001     }
1002 
cancelOverScrollAnimators(boolean onTop)1003     public void cancelOverScrollAnimators(boolean onTop) {
1004         ValueAnimator currentAnimator = onTop ? mTopOverScrollAnimator : mBottomOverScrollAnimator;
1005         if (currentAnimator != null) {
1006             currentAnimator.cancel();
1007         }
1008     }
1009 
1010     /**
1011      * Get the end value of the height animation running on a view or the actualHeight
1012      * if no animation is running.
1013      */
getFinalActualHeight(ExpandableView view)1014     public static int getFinalActualHeight(ExpandableView view) {
1015         if (view == null) {
1016             return 0;
1017         }
1018         ValueAnimator heightAnimator = getChildTag(view, TAG_ANIMATOR_HEIGHT);
1019         if (heightAnimator == null) {
1020             return view.getActualHeight();
1021         } else {
1022             return getChildTag(view, TAG_END_HEIGHT);
1023         }
1024     }
1025 
1026     /**
1027      * Get the end value of the yTranslation animation running on a view or the yTranslation
1028      * if no animation is running.
1029      */
getFinalTranslationY(View view)1030     public static float getFinalTranslationY(View view) {
1031         if (view == null) {
1032             return 0;
1033         }
1034         ValueAnimator yAnimator = getChildTag(view, TAG_ANIMATOR_TRANSLATION_Y);
1035         if (yAnimator == null) {
1036             return view.getTranslationY();
1037         } else {
1038             return getChildTag(view, TAG_END_TRANSLATION_Y);
1039         }
1040     }
1041 
setHeadsUpAppearHeightBottom(int headsUpAppearHeightBottom)1042     public void setHeadsUpAppearHeightBottom(int headsUpAppearHeightBottom) {
1043         mHeadsUpAppearHeightBottom = headsUpAppearHeightBottom;
1044     }
1045 
setShadeExpanded(boolean shadeExpanded)1046     public void setShadeExpanded(boolean shadeExpanded) {
1047         mShadeExpanded = shadeExpanded;
1048     }
1049 }
1050