OSDN Git Service

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