[go: nahoru, domu]

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
17package com.android.systemui.statusbar.stack;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ObjectAnimator;
22import android.animation.PropertyValuesHolder;
23import android.animation.ValueAnimator;
24import android.view.View;
25import android.view.ViewGroup;
26import android.view.animation.Interpolator;
27
28import com.android.systemui.Interpolators;
29import com.android.systemui.R;
30import com.android.systemui.statusbar.ExpandableNotificationRow;
31import com.android.systemui.statusbar.ExpandableView;
32import com.android.systemui.statusbar.policy.HeadsUpManager;
33
34import java.util.ArrayList;
35import java.util.HashSet;
36import java.util.Stack;
37
38/**
39 * An stack state animator which handles animations to new StackScrollStates
40 */
41public 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}
1049