1 /*
2  * Copyright (C) 2022 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 android.window;
18 
19 import android.annotation.NonNull;
20 import android.util.FloatProperty;
21 
22 import com.android.internal.dynamicanimation.animation.DynamicAnimation;
23 import com.android.internal.dynamicanimation.animation.SpringAnimation;
24 import com.android.internal.dynamicanimation.animation.SpringForce;
25 
26 /**
27  * An animator that drives the predictive back progress with a spring.
28  *
29  * The back gesture's latest touch point and committal state determines the final position of
30  * the spring. The continuous movement of the spring is used to produce {@link BackEvent}s with
31  * smoothly transitioning progress values.
32  *
33  * @hide
34  */
35 public class BackProgressAnimator {
36     /**
37      *  A factor to scale the input progress by, so that it works better with the spring.
38      *  We divide the output progress by this value before sending it to apps, so that apps
39      *  always receive progress values in [0, 1].
40      */
41     private static final float SCALE_FACTOR = 100f;
42     private final SpringAnimation mSpring;
43     private ProgressCallback mCallback;
44     private float mProgress = 0;
45     private BackMotionEvent mLastBackEvent;
46     private boolean mBackAnimationInProgress = false;
47 
setProgress(float progress)48     private void setProgress(float progress) {
49         mProgress = progress;
50     }
51 
getProgress()52     private float getProgress() {
53         return mProgress;
54     }
55 
56     private static final FloatProperty<BackProgressAnimator> PROGRESS_PROP =
57             new FloatProperty<BackProgressAnimator>("progress") {
58                 @Override
59                 public void setValue(BackProgressAnimator animator, float value) {
60                     animator.setProgress(value);
61                     animator.updateProgressValue(value);
62                 }
63 
64                 @Override
65                 public Float get(BackProgressAnimator object) {
66                     return object.getProgress();
67                 }
68             };
69 
70 
71     /** A callback to be invoked when there's a progress value update from the animator. */
72     public interface ProgressCallback {
73         /** Called when there's a progress value update. */
onProgressUpdate(BackEvent event)74         void onProgressUpdate(BackEvent event);
75     }
76 
BackProgressAnimator()77     public BackProgressAnimator() {
78         mSpring = new SpringAnimation(this, PROGRESS_PROP);
79         mSpring.setSpring(new SpringForce()
80                 .setStiffness(SpringForce.STIFFNESS_MEDIUM)
81                 .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY));
82     }
83 
84     /**
85      * Sets a new target position for the back progress.
86      *
87      * @param event the {@link BackMotionEvent} containing the latest target progress.
88      */
onBackProgressed(BackMotionEvent event)89     public void onBackProgressed(BackMotionEvent event) {
90         if (!mBackAnimationInProgress) {
91             return;
92         }
93         mLastBackEvent = event;
94         if (mSpring == null) {
95             return;
96         }
97         mSpring.animateToFinalPosition(event.getProgress() * SCALE_FACTOR);
98     }
99 
100     /**
101      * Starts the back progress animation.
102      *
103      * @param event the {@link BackMotionEvent} that started the gesture.
104      * @param callback the back callback to invoke for the gesture. It will receive back progress
105      *                 dispatches as the progress animation updates.
106      */
onBackStarted(BackMotionEvent event, ProgressCallback callback)107     public void onBackStarted(BackMotionEvent event, ProgressCallback callback) {
108         reset();
109         mLastBackEvent = event;
110         mCallback = callback;
111         mBackAnimationInProgress = true;
112     }
113 
114     /**
115      * Resets the back progress animation. This should be called when back is invoked or cancelled.
116      */
reset()117     public void reset() {
118         mSpring.animateToFinalPosition(0);
119         if (mSpring.canSkipToEnd()) {
120             mSpring.skipToEnd();
121         } else {
122             // Should never happen.
123             mSpring.cancel();
124         }
125         mBackAnimationInProgress = false;
126         mLastBackEvent = null;
127         mCallback = null;
128         mProgress = 0;
129     }
130 
131     /**
132      * Animate the back progress animation from current progress to start position.
133      * This should be called when back is cancelled.
134      *
135      * @param finishCallback the callback to be invoked when the progress is reach to 0.
136      */
onBackCancelled(@onNull Runnable finishCallback)137     public void onBackCancelled(@NonNull Runnable finishCallback) {
138         final DynamicAnimation.OnAnimationEndListener listener =
139                 new DynamicAnimation.OnAnimationEndListener() {
140             @Override
141             public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value,
142                     float velocity) {
143                 mSpring.removeEndListener(this);
144                 finishCallback.run();
145                 reset();
146             }
147         };
148         mSpring.addEndListener(listener);
149         mSpring.animateToFinalPosition(0);
150     }
151 
152     /** Returns true if the back animation is in progress. */
isBackAnimationInProgress()153     boolean isBackAnimationInProgress() {
154         return mBackAnimationInProgress;
155     }
156 
updateProgressValue(float progress)157     private void updateProgressValue(float progress) {
158         if (mLastBackEvent == null || mCallback == null || !mBackAnimationInProgress) {
159             return;
160         }
161         mCallback.onProgressUpdate(
162                 new BackEvent(mLastBackEvent.getTouchX(), mLastBackEvent.getTouchY(),
163                         progress / SCALE_FACTOR, mLastBackEvent.getSwipeEdge()));
164     }
165 
166 }
167