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