1 /*
2  * Copyright (C) 2015 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.notification.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.util.Property;
25 import android.view.View;
26 import android.view.animation.Interpolator;
27 
28 import com.android.systemui.Dumpable;
29 import com.android.systemui.Interpolators;
30 import com.android.systemui.R;
31 import com.android.systemui.statusbar.notification.AnimatableProperty;
32 import com.android.systemui.statusbar.notification.PropertyAnimator;
33 import com.android.systemui.statusbar.notification.row.ExpandableView;
34 import com.android.systemui.statusbar.policy.HeadsUpUtil;
35 
36 import java.io.FileDescriptor;
37 import java.io.PrintWriter;
38 import java.lang.reflect.Field;
39 import java.lang.reflect.Modifier;
40 
41 /**
42  * A state of a view. This can be used to apply a set of view properties to a view with
43  * {@link com.android.systemui.statusbar.notification.stack.StackScrollState} or start
44  * animations with {@link com.android.systemui.statusbar.notification.stack.StackStateAnimator}.
45 */
46 public class ViewState implements Dumpable {
47 
48     /**
49      * Some animation properties that can be used to update running animations but not creating
50      * any new ones.
51      */
52     protected static final AnimationProperties NO_NEW_ANIMATIONS = new AnimationProperties() {
53         AnimationFilter mAnimationFilter = new AnimationFilter();
54         @Override
55         public AnimationFilter getAnimationFilter() {
56             return mAnimationFilter;
57         }
58     };
59     private static final int TAG_ANIMATOR_TRANSLATION_X = R.id.translation_x_animator_tag;
60     private static final int TAG_ANIMATOR_TRANSLATION_Y = R.id.translation_y_animator_tag;
61     private static final int TAG_ANIMATOR_TRANSLATION_Z = R.id.translation_z_animator_tag;
62     private static final int TAG_ANIMATOR_ALPHA = R.id.alpha_animator_tag;
63     private static final int TAG_END_TRANSLATION_X = R.id.translation_x_animator_end_value_tag;
64     private static final int TAG_END_TRANSLATION_Y = R.id.translation_y_animator_end_value_tag;
65     private static final int TAG_END_TRANSLATION_Z = R.id.translation_z_animator_end_value_tag;
66     private static final int TAG_END_ALPHA = R.id.alpha_animator_end_value_tag;
67     private static final int TAG_START_TRANSLATION_X = R.id.translation_x_animator_start_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 
72     private static final AnimatableProperty SCALE_X_PROPERTY
73             = new AnimatableProperty() {
74 
75         @Override
76         public int getAnimationStartTag() {
77             return R.id.scale_x_animator_start_value_tag;
78         }
79 
80         @Override
81         public int getAnimationEndTag() {
82             return R.id.scale_x_animator_end_value_tag;
83         }
84 
85         @Override
86         public int getAnimatorTag() {
87             return R.id.scale_x_animator_tag;
88         }
89 
90         @Override
91         public Property getProperty() {
92             return View.SCALE_X;
93         }
94     };
95 
96     private static final AnimatableProperty SCALE_Y_PROPERTY
97             = new AnimatableProperty() {
98 
99         @Override
100         public int getAnimationStartTag() {
101             return R.id.scale_y_animator_start_value_tag;
102         }
103 
104         @Override
105         public int getAnimationEndTag() {
106             return R.id.scale_y_animator_end_value_tag;
107         }
108 
109         @Override
110         public int getAnimatorTag() {
111             return R.id.scale_y_animator_tag;
112         }
113 
114         @Override
115         public Property getProperty() {
116             return View.SCALE_Y;
117         }
118     };
119 
120     public float alpha;
121     public float xTranslation;
122     public float yTranslation;
123     public float zTranslation;
124     public boolean gone;
125     public boolean hidden;
126     public float scaleX = 1.0f;
127     public float scaleY = 1.0f;
128 
copyFrom(ViewState viewState)129     public void copyFrom(ViewState viewState) {
130         alpha = viewState.alpha;
131         xTranslation = viewState.xTranslation;
132         yTranslation = viewState.yTranslation;
133         zTranslation = viewState.zTranslation;
134         gone = viewState.gone;
135         hidden = viewState.hidden;
136         scaleX = viewState.scaleX;
137         scaleY = viewState.scaleY;
138     }
139 
initFrom(View view)140     public void initFrom(View view) {
141         alpha = view.getAlpha();
142         xTranslation = view.getTranslationX();
143         yTranslation = view.getTranslationY();
144         zTranslation = view.getTranslationZ();
145         gone = view.getVisibility() == View.GONE;
146         hidden = view.getVisibility() == View.INVISIBLE;
147         scaleX = view.getScaleX();
148         scaleY = view.getScaleY();
149     }
150 
151     /**
152      * Applies a {@link ViewState} to a normal view.
153      */
applyToView(View view)154     public void applyToView(View view) {
155         if (this.gone) {
156             // don't do anything with it
157             return;
158         }
159 
160         // apply xTranslation
161         boolean animatingX = isAnimating(view, TAG_ANIMATOR_TRANSLATION_X);
162         if (animatingX) {
163             updateAnimationX(view);
164         } else if (view.getTranslationX() != this.xTranslation){
165             view.setTranslationX(this.xTranslation);
166         }
167 
168         // apply yTranslation
169         boolean animatingY = isAnimating(view, TAG_ANIMATOR_TRANSLATION_Y);
170         if (animatingY) {
171             updateAnimationY(view);
172         } else if (view.getTranslationY() != this.yTranslation) {
173             view.setTranslationY(this.yTranslation);
174         }
175 
176         // apply zTranslation
177         boolean animatingZ = isAnimating(view, TAG_ANIMATOR_TRANSLATION_Z);
178         if (animatingZ) {
179             updateAnimationZ(view);
180         } else if (view.getTranslationZ() != this.zTranslation) {
181             view.setTranslationZ(this.zTranslation);
182         }
183 
184         // apply scaleX
185         boolean animatingScaleX = isAnimating(view, SCALE_X_PROPERTY);
186         if (animatingScaleX) {
187             updateAnimation(view, SCALE_X_PROPERTY, scaleX);
188         } else if (view.getScaleX() != scaleX) {
189             view.setScaleX(scaleX);
190         }
191 
192         // apply scaleY
193         boolean animatingScaleY = isAnimating(view, SCALE_Y_PROPERTY);
194         if (animatingScaleY) {
195             updateAnimation(view, SCALE_Y_PROPERTY, scaleY);
196         } else if (view.getScaleY() != scaleY) {
197             view.setScaleY(scaleY);
198         }
199 
200         int oldVisibility = view.getVisibility();
201         boolean becomesInvisible = this.alpha == 0.0f
202                 || (this.hidden && (!isAnimating(view) || oldVisibility != View.VISIBLE));
203         boolean animatingAlpha = isAnimating(view, TAG_ANIMATOR_ALPHA);
204         if (animatingAlpha) {
205             updateAlphaAnimation(view);
206         } else if (view.getAlpha() != this.alpha) {
207             // apply layer type
208             boolean becomesFullyVisible = this.alpha == 1.0f;
209             boolean newLayerTypeIsHardware = !becomesInvisible && !becomesFullyVisible
210                     && view.hasOverlappingRendering();
211             int layerType = view.getLayerType();
212             int newLayerType = newLayerTypeIsHardware
213                     ? View.LAYER_TYPE_HARDWARE
214                     : View.LAYER_TYPE_NONE;
215             if (layerType != newLayerType) {
216                 view.setLayerType(newLayerType, null);
217             }
218 
219             // apply alpha
220             view.setAlpha(this.alpha);
221         }
222 
223         // apply visibility
224         int newVisibility = becomesInvisible ? View.INVISIBLE : View.VISIBLE;
225         if (newVisibility != oldVisibility) {
226             if (!(view instanceof ExpandableView) || !((ExpandableView) view).willBeGone()) {
227                 // We don't want views to change visibility when they are animating to GONE
228                 view.setVisibility(newVisibility);
229             }
230         }
231     }
232 
isAnimating(View view)233     public boolean isAnimating(View view) {
234         if (isAnimating(view, TAG_ANIMATOR_TRANSLATION_X)) {
235             return true;
236         }
237         if (isAnimating(view, TAG_ANIMATOR_TRANSLATION_Y)) {
238             return true;
239         }
240         if (isAnimating(view, TAG_ANIMATOR_TRANSLATION_Z)) {
241             return true;
242         }
243         if (isAnimating(view, TAG_ANIMATOR_ALPHA)) {
244             return true;
245         }
246         if (isAnimating(view, SCALE_X_PROPERTY)) {
247             return true;
248         }
249         if (isAnimating(view, SCALE_Y_PROPERTY)) {
250             return true;
251         }
252         return false;
253     }
254 
isAnimating(View view, int tag)255     private static boolean isAnimating(View view, int tag) {
256         return getChildTag(view, tag) != null;
257     }
258 
isAnimating(View view, AnimatableProperty property)259     public static boolean isAnimating(View view, AnimatableProperty property) {
260         return getChildTag(view, property.getAnimatorTag()) != null;
261     }
262 
263     /**
264      * Start an animation to this viewstate
265      * @param child the view to animate
266      * @param animationProperties the properties of the animation
267      */
animateTo(View child, AnimationProperties animationProperties)268     public void animateTo(View child, AnimationProperties animationProperties) {
269         boolean wasVisible = child.getVisibility() == View.VISIBLE;
270         final float alpha = this.alpha;
271         if (!wasVisible && (alpha != 0 || child.getAlpha() != 0)
272                 && !this.gone && !this.hidden) {
273             child.setVisibility(View.VISIBLE);
274         }
275         float childAlpha = child.getAlpha();
276         boolean alphaChanging = this.alpha != childAlpha;
277         if (child instanceof ExpandableView) {
278             // We don't want views to change visibility when they are animating to GONE
279             alphaChanging &= !((ExpandableView) child).willBeGone();
280         }
281 
282         // start translationX animation
283         if (child.getTranslationX() != this.xTranslation) {
284             startXTranslationAnimation(child, animationProperties);
285         } else {
286             abortAnimation(child, TAG_ANIMATOR_TRANSLATION_X);
287         }
288 
289         // start translationY animation
290         if (child.getTranslationY() != this.yTranslation) {
291             startYTranslationAnimation(child, animationProperties);
292         } else {
293             abortAnimation(child, TAG_ANIMATOR_TRANSLATION_Y);
294         }
295 
296         // start translationZ animation
297         if (child.getTranslationZ() != this.zTranslation) {
298             startZTranslationAnimation(child, animationProperties);
299         } else {
300             abortAnimation(child, TAG_ANIMATOR_TRANSLATION_Z);
301         }
302 
303         // start scaleX animation
304         if (child.getScaleX() != scaleX) {
305             PropertyAnimator.startAnimation(child, SCALE_X_PROPERTY, scaleX, animationProperties);
306         } else {
307             abortAnimation(child, SCALE_X_PROPERTY.getAnimatorTag());
308         }
309 
310         // start scaleX animation
311         if (child.getScaleY() != scaleY) {
312             PropertyAnimator.startAnimation(child, SCALE_Y_PROPERTY, scaleY, animationProperties);
313         } else {
314             abortAnimation(child, SCALE_Y_PROPERTY.getAnimatorTag());
315         }
316 
317         // start alpha animation
318         if (alphaChanging) {
319             startAlphaAnimation(child, animationProperties);
320         }  else {
321             abortAnimation(child, TAG_ANIMATOR_ALPHA);
322         }
323     }
324 
updateAlphaAnimation(View view)325     private void updateAlphaAnimation(View view) {
326         startAlphaAnimation(view, NO_NEW_ANIMATIONS);
327     }
328 
startAlphaAnimation(final View child, AnimationProperties properties)329     private void startAlphaAnimation(final View child, AnimationProperties properties) {
330         Float previousStartValue = getChildTag(child,TAG_START_ALPHA);
331         Float previousEndValue = getChildTag(child,TAG_END_ALPHA);
332         final float newEndValue = this.alpha;
333         if (previousEndValue != null && previousEndValue == newEndValue) {
334             return;
335         }
336         ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_ALPHA);
337         AnimationFilter filter = properties.getAnimationFilter();
338         if (!filter.animateAlpha) {
339             // just a local update was performed
340             if (previousAnimator != null) {
341                 // we need to increase all animation keyframes of the previous animator by the
342                 // relative change to the end value
343                 PropertyValuesHolder[] values = previousAnimator.getValues();
344                 float relativeDiff = newEndValue - previousEndValue;
345                 float newStartValue = previousStartValue + relativeDiff;
346                 values[0].setFloatValues(newStartValue, newEndValue);
347                 child.setTag(TAG_START_ALPHA, newStartValue);
348                 child.setTag(TAG_END_ALPHA, newEndValue);
349                 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
350                 return;
351             } else {
352                 // no new animation needed, let's just apply the value
353                 child.setAlpha(newEndValue);
354                 if (newEndValue == 0) {
355                     child.setVisibility(View.INVISIBLE);
356                 }
357             }
358         }
359 
360         ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.ALPHA,
361                 child.getAlpha(), newEndValue);
362         animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
363         // Handle layer type
364         child.setLayerType(View.LAYER_TYPE_HARDWARE, null);
365         animator.addListener(new AnimatorListenerAdapter() {
366             public boolean mWasCancelled;
367 
368             @Override
369             public void onAnimationEnd(Animator animation) {
370                 child.setLayerType(View.LAYER_TYPE_NONE, null);
371                 if (newEndValue == 0 && !mWasCancelled) {
372                     child.setVisibility(View.INVISIBLE);
373                 }
374                 // remove the tag when the animation is finished
375                 child.setTag(TAG_ANIMATOR_ALPHA, null);
376                 child.setTag(TAG_START_ALPHA, null);
377                 child.setTag(TAG_END_ALPHA, null);
378             }
379 
380             @Override
381             public void onAnimationCancel(Animator animation) {
382                 mWasCancelled = true;
383             }
384 
385             @Override
386             public void onAnimationStart(Animator animation) {
387                 mWasCancelled = false;
388             }
389         });
390         long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator);
391         animator.setDuration(newDuration);
392         if (properties.delay > 0 && (previousAnimator == null
393                 || previousAnimator.getAnimatedFraction() == 0)) {
394             animator.setStartDelay(properties.delay);
395         }
396         AnimatorListenerAdapter listener = properties.getAnimationFinishListener();
397         if (listener != null) {
398             animator.addListener(listener);
399         }
400 
401         startAnimator(animator, listener);
402         child.setTag(TAG_ANIMATOR_ALPHA, animator);
403         child.setTag(TAG_START_ALPHA, child.getAlpha());
404         child.setTag(TAG_END_ALPHA, newEndValue);
405     }
406 
updateAnimationZ(View view)407     private void updateAnimationZ(View view) {
408         startZTranslationAnimation(view, NO_NEW_ANIMATIONS);
409     }
410 
updateAnimation(View view, AnimatableProperty property, float endValue)411     private void updateAnimation(View view, AnimatableProperty property,
412             float endValue) {
413         PropertyAnimator.startAnimation(view, property, endValue, NO_NEW_ANIMATIONS);
414     }
415 
startZTranslationAnimation(final View child, AnimationProperties properties)416     private void startZTranslationAnimation(final View child, AnimationProperties properties) {
417         Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Z);
418         Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Z);
419         float newEndValue = this.zTranslation;
420         if (previousEndValue != null && previousEndValue == newEndValue) {
421             return;
422         }
423         ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Z);
424         AnimationFilter filter = properties.getAnimationFilter();
425         if (!filter.animateZ) {
426             // just a local update was performed
427             if (previousAnimator != null) {
428                 // we need to increase all animation keyframes of the previous animator by the
429                 // relative change to the end value
430                 PropertyValuesHolder[] values = previousAnimator.getValues();
431                 float relativeDiff = newEndValue - previousEndValue;
432                 float newStartValue = previousStartValue + relativeDiff;
433                 values[0].setFloatValues(newStartValue, newEndValue);
434                 child.setTag(TAG_START_TRANSLATION_Z, newStartValue);
435                 child.setTag(TAG_END_TRANSLATION_Z, newEndValue);
436                 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
437                 return;
438             } else {
439                 // no new animation needed, let's just apply the value
440                 child.setTranslationZ(newEndValue);
441             }
442         }
443 
444         ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Z,
445                 child.getTranslationZ(), newEndValue);
446         animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
447         long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator);
448         animator.setDuration(newDuration);
449         if (properties.delay > 0 && (previousAnimator == null
450                 || previousAnimator.getAnimatedFraction() == 0)) {
451             animator.setStartDelay(properties.delay);
452         }
453         AnimatorListenerAdapter listener = properties.getAnimationFinishListener();
454         if (listener != null) {
455             animator.addListener(listener);
456         }
457         // remove the tag when the animation is finished
458         animator.addListener(new AnimatorListenerAdapter() {
459             @Override
460             public void onAnimationEnd(Animator animation) {
461                 child.setTag(TAG_ANIMATOR_TRANSLATION_Z, null);
462                 child.setTag(TAG_START_TRANSLATION_Z, null);
463                 child.setTag(TAG_END_TRANSLATION_Z, null);
464             }
465         });
466         startAnimator(animator, listener);
467         child.setTag(TAG_ANIMATOR_TRANSLATION_Z, animator);
468         child.setTag(TAG_START_TRANSLATION_Z, child.getTranslationZ());
469         child.setTag(TAG_END_TRANSLATION_Z, newEndValue);
470     }
471 
updateAnimationX(View view)472     private void updateAnimationX(View view) {
473         startXTranslationAnimation(view, NO_NEW_ANIMATIONS);
474     }
475 
startXTranslationAnimation(final View child, AnimationProperties properties)476     private void startXTranslationAnimation(final View child, AnimationProperties properties) {
477         Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_X);
478         Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_X);
479         float newEndValue = this.xTranslation;
480         if (previousEndValue != null && previousEndValue == newEndValue) {
481             return;
482         }
483         ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_X);
484         AnimationFilter filter = properties.getAnimationFilter();
485         if (!filter.animateX) {
486             // just a local update was performed
487             if (previousAnimator != null) {
488                 // we need to increase all animation keyframes of the previous animator by the
489                 // relative change to the end value
490                 PropertyValuesHolder[] values = previousAnimator.getValues();
491                 float relativeDiff = newEndValue - previousEndValue;
492                 float newStartValue = previousStartValue + relativeDiff;
493                 values[0].setFloatValues(newStartValue, newEndValue);
494                 child.setTag(TAG_START_TRANSLATION_X, newStartValue);
495                 child.setTag(TAG_END_TRANSLATION_X, newEndValue);
496                 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
497                 return;
498             } else {
499                 // no new animation needed, let's just apply the value
500                 child.setTranslationX(newEndValue);
501                 return;
502             }
503         }
504 
505         ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_X,
506                 child.getTranslationX(), newEndValue);
507         Interpolator customInterpolator = properties.getCustomInterpolator(child,
508                 View.TRANSLATION_X);
509         Interpolator interpolator =  customInterpolator != null ? customInterpolator
510                 : Interpolators.FAST_OUT_SLOW_IN;
511         animator.setInterpolator(interpolator);
512         long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator);
513         animator.setDuration(newDuration);
514         if (properties.delay > 0 && (previousAnimator == null
515                 || previousAnimator.getAnimatedFraction() == 0)) {
516             animator.setStartDelay(properties.delay);
517         }
518         AnimatorListenerAdapter listener = properties.getAnimationFinishListener();
519         if (listener != null) {
520             animator.addListener(listener);
521         }
522         // remove the tag when the animation is finished
523         animator.addListener(new AnimatorListenerAdapter() {
524             @Override
525             public void onAnimationEnd(Animator animation) {
526                 child.setTag(TAG_ANIMATOR_TRANSLATION_X, null);
527                 child.setTag(TAG_START_TRANSLATION_X, null);
528                 child.setTag(TAG_END_TRANSLATION_X, null);
529             }
530         });
531         startAnimator(animator, listener);
532         child.setTag(TAG_ANIMATOR_TRANSLATION_X, animator);
533         child.setTag(TAG_START_TRANSLATION_X, child.getTranslationX());
534         child.setTag(TAG_END_TRANSLATION_X, newEndValue);
535     }
536 
updateAnimationY(View view)537     private void updateAnimationY(View view) {
538         startYTranslationAnimation(view, NO_NEW_ANIMATIONS);
539     }
540 
startYTranslationAnimation(final View child, AnimationProperties properties)541     private void startYTranslationAnimation(final View child, AnimationProperties properties) {
542         Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Y);
543         Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Y);
544         float newEndValue = this.yTranslation;
545         if (previousEndValue != null && previousEndValue == newEndValue) {
546             return;
547         }
548         ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y);
549         AnimationFilter filter = properties.getAnimationFilter();
550         if (!filter.shouldAnimateY(child)) {
551             // just a local update was performed
552             if (previousAnimator != null) {
553                 // we need to increase all animation keyframes of the previous animator by the
554                 // relative change to the end value
555                 PropertyValuesHolder[] values = previousAnimator.getValues();
556                 float relativeDiff = newEndValue - previousEndValue;
557                 float newStartValue = previousStartValue + relativeDiff;
558                 values[0].setFloatValues(newStartValue, newEndValue);
559                 child.setTag(TAG_START_TRANSLATION_Y, newStartValue);
560                 child.setTag(TAG_END_TRANSLATION_Y, newEndValue);
561                 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
562                 return;
563             } else {
564                 // no new animation needed, let's just apply the value
565                 child.setTranslationY(newEndValue);
566                 return;
567             }
568         }
569 
570         ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Y,
571                 child.getTranslationY(), newEndValue);
572         Interpolator customInterpolator = properties.getCustomInterpolator(child,
573                 View.TRANSLATION_Y);
574         Interpolator interpolator =  customInterpolator != null ? customInterpolator
575                 : Interpolators.FAST_OUT_SLOW_IN;
576         animator.setInterpolator(interpolator);
577         long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator);
578         animator.setDuration(newDuration);
579         if (properties.delay > 0 && (previousAnimator == null
580                 || previousAnimator.getAnimatedFraction() == 0)) {
581             animator.setStartDelay(properties.delay);
582         }
583         AnimatorListenerAdapter listener = properties.getAnimationFinishListener();
584         if (listener != null) {
585             animator.addListener(listener);
586         }
587         // remove the tag when the animation is finished
588         animator.addListener(new AnimatorListenerAdapter() {
589             @Override
590             public void onAnimationEnd(Animator animation) {
591                 HeadsUpUtil.setIsClickedHeadsUpNotification(child, false);
592                 child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null);
593                 child.setTag(TAG_START_TRANSLATION_Y, null);
594                 child.setTag(TAG_END_TRANSLATION_Y, null);
595                 onYTranslationAnimationFinished(child);
596             }
597         });
598         startAnimator(animator, listener);
599         child.setTag(TAG_ANIMATOR_TRANSLATION_Y, animator);
600         child.setTag(TAG_START_TRANSLATION_Y, child.getTranslationY());
601         child.setTag(TAG_END_TRANSLATION_Y, newEndValue);
602     }
603 
onYTranslationAnimationFinished(View view)604     protected void onYTranslationAnimationFinished(View view) {
605         if (hidden && !gone) {
606             view.setVisibility(View.INVISIBLE);
607         }
608     }
609 
startAnimator(Animator animator, AnimatorListenerAdapter listener)610     public static void startAnimator(Animator animator, AnimatorListenerAdapter listener) {
611         if (listener != null) {
612             // Even if there's a delay we'd want to notify it of the start immediately.
613             listener.onAnimationStart(animator);
614         }
615         animator.start();
616     }
617 
getChildTag(View child, int tag)618     public static <T> T getChildTag(View child, int tag) {
619         return (T) child.getTag(tag);
620     }
621 
abortAnimation(View child, int animatorTag)622     protected void abortAnimation(View child, int animatorTag) {
623         Animator previousAnimator = getChildTag(child, animatorTag);
624         if (previousAnimator != null) {
625             previousAnimator.cancel();
626         }
627     }
628 
629     /**
630      * Cancel the previous animator and get the duration of the new animation.
631      *
632      * @param duration the new duration
633      * @param previousAnimator the animator which was running before
634      * @return the new duration
635      */
cancelAnimatorAndGetNewDuration(long duration, ValueAnimator previousAnimator)636     public static long cancelAnimatorAndGetNewDuration(long duration,
637             ValueAnimator previousAnimator) {
638         long newDuration = duration;
639         if (previousAnimator != null) {
640             // We take either the desired length of the new animation or the remaining time of
641             // the previous animator, whichever is longer.
642             newDuration = Math.max(previousAnimator.getDuration()
643                     - previousAnimator.getCurrentPlayTime(), newDuration);
644             previousAnimator.cancel();
645         }
646         return newDuration;
647     }
648 
649     /**
650      * Get the end value of the xTranslation animation running on a view or the xTranslation
651      * if no animation is running.
652      */
getFinalTranslationX(View view)653     public static float getFinalTranslationX(View view) {
654         if (view == null) {
655             return 0;
656         }
657         ValueAnimator xAnimator = getChildTag(view, TAG_ANIMATOR_TRANSLATION_X);
658         if (xAnimator == null) {
659             return view.getTranslationX();
660         } else {
661             return getChildTag(view, TAG_END_TRANSLATION_X);
662         }
663     }
664 
665     /**
666      * Get the end value of the yTranslation animation running on a view or the yTranslation
667      * if no animation is running.
668      */
getFinalTranslationY(View view)669     public static float getFinalTranslationY(View view) {
670         if (view == null) {
671             return 0;
672         }
673         ValueAnimator yAnimator = getChildTag(view, TAG_ANIMATOR_TRANSLATION_Y);
674         if (yAnimator == null) {
675             return view.getTranslationY();
676         } else {
677             return getChildTag(view, TAG_END_TRANSLATION_Y);
678         }
679     }
680 
681     /**
682      * Get the end value of the zTranslation animation running on a view or the zTranslation
683      * if no animation is running.
684      */
getFinalTranslationZ(View view)685     public static float getFinalTranslationZ(View view) {
686         if (view == null) {
687             return 0;
688         }
689         ValueAnimator zAnimator = getChildTag(view, TAG_ANIMATOR_TRANSLATION_Z);
690         if (zAnimator == null) {
691             return view.getTranslationZ();
692         } else {
693             return getChildTag(view, TAG_END_TRANSLATION_Z);
694         }
695     }
696 
isAnimatingY(View child)697     public static boolean isAnimatingY(View child) {
698         return getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y) != null;
699     }
700 
cancelAnimations(View view)701     public void cancelAnimations(View view) {
702         Animator animator = getChildTag(view, TAG_ANIMATOR_TRANSLATION_X);
703         if (animator != null) {
704             animator.cancel();
705         }
706         animator = getChildTag(view, TAG_ANIMATOR_TRANSLATION_Y);
707         if (animator != null) {
708             animator.cancel();
709         }
710         animator = getChildTag(view, TAG_ANIMATOR_TRANSLATION_Z);
711         if (animator != null) {
712             animator.cancel();
713         }
714         animator = getChildTag(view, TAG_ANIMATOR_ALPHA);
715         if (animator != null) {
716             animator.cancel();
717         }
718     }
719 
720     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)721     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
722         StringBuilder result = new StringBuilder();
723         result.append("ViewState { ");
724 
725         boolean first = true;
726         Class currentClass = this.getClass();
727         while (currentClass != null) {
728             Field[] fields = currentClass.getDeclaredFields();
729             // Print field names paired with their values
730             for (Field field : fields) {
731                 int modifiers = field.getModifiers();
732                 if (Modifier.isStatic(modifiers) || field.isSynthetic()
733                         || Modifier.isTransient(modifiers)) {
734                     continue;
735                 }
736                 if (!first) {
737                     result.append(", ");
738                 }
739                 try {
740                     result.append(field.getName());
741                     result.append(": ");
742                     //requires access to private field:
743                     field.setAccessible(true);
744                     result.append(field.get(this));
745                 } catch (IllegalAccessException ex) {
746                 }
747                 first = false;
748             }
749             currentClass = currentClass.getSuperclass();
750         }
751         result.append(" }");
752         pw.print(result);
753     }
754 }
755