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