OSDN Git Service

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