OSDN Git Service

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