OSDN Git Service

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