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