1 /*
2  * Copyright (C) 2020 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.wm.shell.onehanded;
18 
19 import android.animation.Animator;
20 import android.animation.ValueAnimator;
21 import android.annotation.IntDef;
22 import android.annotation.Nullable;
23 import android.content.Context;
24 import android.graphics.Rect;
25 import android.view.SurfaceControl;
26 import android.view.animation.BaseInterpolator;
27 import android.window.WindowContainerToken;
28 
29 import androidx.annotation.NonNull;
30 import androidx.annotation.VisibleForTesting;
31 
32 import java.io.PrintWriter;
33 import java.lang.annotation.Retention;
34 import java.lang.annotation.RetentionPolicy;
35 import java.util.ArrayList;
36 import java.util.HashMap;
37 import java.util.List;
38 
39 /**
40  * Controller class of OneHanded animations (both from and to OneHanded mode).
41  */
42 public class OneHandedAnimationController {
43     private static final String TAG = "OneHandedAnimationController";
44     private static final float FRACTION_START = 0f;
45     private static final float FRACTION_END = 1f;
46 
47     public static final int TRANSITION_DIRECTION_NONE = 0;
48     public static final int TRANSITION_DIRECTION_TRIGGER = 1;
49     public static final int TRANSITION_DIRECTION_EXIT = 2;
50 
51     @IntDef(prefix = {"TRANSITION_DIRECTION_"}, value = {
52             TRANSITION_DIRECTION_NONE,
53             TRANSITION_DIRECTION_TRIGGER,
54             TRANSITION_DIRECTION_EXIT,
55     })
56     @Retention(RetentionPolicy.SOURCE)
57     public @interface TransitionDirection {
58     }
59 
60     private final OneHandedInterpolator mInterpolator;
61     private final OneHandedSurfaceTransactionHelper mSurfaceTransactionHelper;
62     private final HashMap<WindowContainerToken, OneHandedTransitionAnimator> mAnimatorMap =
63             new HashMap<>();
64 
65     /**
66      * Constructor of OneHandedAnimationController
67      */
OneHandedAnimationController(Context context)68     public OneHandedAnimationController(Context context) {
69         mSurfaceTransactionHelper = new OneHandedSurfaceTransactionHelper(context);
70         mInterpolator = new OneHandedInterpolator();
71     }
72 
73     @SuppressWarnings("unchecked")
getAnimator(WindowContainerToken token, SurfaceControl leash, float startPos, float endPos, Rect displayBounds)74     OneHandedTransitionAnimator getAnimator(WindowContainerToken token, SurfaceControl leash,
75             float startPos, float endPos, Rect displayBounds) {
76         final OneHandedTransitionAnimator animator = mAnimatorMap.get(token);
77         if (animator == null) {
78             mAnimatorMap.put(token, setupOneHandedTransitionAnimator(
79                     OneHandedTransitionAnimator.ofYOffset(token, leash, startPos, endPos,
80                             displayBounds)));
81         } else if (animator.isRunning()) {
82             animator.updateEndValue(endPos);
83         } else {
84             animator.cancel();
85             mAnimatorMap.put(token, setupOneHandedTransitionAnimator(
86                     OneHandedTransitionAnimator.ofYOffset(token, leash, startPos, endPos,
87                             displayBounds)));
88         }
89         return mAnimatorMap.get(token);
90     }
91 
getAnimatorMap()92     HashMap<WindowContainerToken, OneHandedTransitionAnimator> getAnimatorMap() {
93         return mAnimatorMap;
94     }
95 
isAnimatorsConsumed()96     boolean isAnimatorsConsumed() {
97         return mAnimatorMap.isEmpty();
98     }
99 
removeAnimator(WindowContainerToken token)100     void removeAnimator(WindowContainerToken token) {
101         final OneHandedTransitionAnimator animator = mAnimatorMap.remove(token);
102         if (animator != null && animator.isRunning()) {
103             animator.cancel();
104         }
105     }
106 
setupOneHandedTransitionAnimator( OneHandedTransitionAnimator animator)107     OneHandedTransitionAnimator setupOneHandedTransitionAnimator(
108             OneHandedTransitionAnimator animator) {
109         animator.setSurfaceTransactionHelper(mSurfaceTransactionHelper);
110         animator.setInterpolator(mInterpolator);
111         animator.setFloatValues(FRACTION_START, FRACTION_END);
112         return animator;
113     }
114 
115     /**
116      * Animator for OneHanded transition animation which supports both alpha and bounds animation.
117      */
118     // TODO: Refactoring to use SpringAnimation and DynamicAnimation instead of using ValueAnimator
119     //  to implement One-Handed transition animation. (b/185129031)
120     public abstract static class OneHandedTransitionAnimator extends ValueAnimator implements
121             ValueAnimator.AnimatorUpdateListener,
122             ValueAnimator.AnimatorListener {
123 
124         private final SurfaceControl mLeash;
125         private final WindowContainerToken mToken;
126         private float mStartValue;
127         private float mEndValue;
128         private float mCurrentValue;
129 
130         private final List<OneHandedAnimationCallback> mOneHandedAnimationCallbacks =
131                 new ArrayList<>();
132         private OneHandedSurfaceTransactionHelper mSurfaceTransactionHelper;
133         private OneHandedSurfaceTransactionHelper.SurfaceControlTransactionFactory
134                 mSurfaceControlTransactionFactory;
135 
136         private @TransitionDirection int mTransitionDirection;
137 
OneHandedTransitionAnimator(WindowContainerToken token, SurfaceControl leash, float startValue, float endValue)138         private OneHandedTransitionAnimator(WindowContainerToken token, SurfaceControl leash,
139                 float startValue, float endValue) {
140             mLeash = leash;
141             mToken = token;
142             mStartValue = startValue;
143             mEndValue = endValue;
144             addListener(this);
145             addUpdateListener(this);
146             mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
147             mTransitionDirection = TRANSITION_DIRECTION_NONE;
148         }
149 
150         @Override
onAnimationStart(Animator animation)151         public void onAnimationStart(Animator animation) {
152             mCurrentValue = mStartValue;
153             mOneHandedAnimationCallbacks.forEach(
154                     (callback) -> callback.onOneHandedAnimationStart(this)
155             );
156         }
157 
158         @Override
onAnimationEnd(Animator animation)159         public void onAnimationEnd(Animator animation) {
160             mCurrentValue = mEndValue;
161             final SurfaceControl.Transaction tx = newSurfaceControlTransaction();
162             onEndTransaction(mLeash, tx);
163             mOneHandedAnimationCallbacks.forEach(
164                     (callback) -> callback.onOneHandedAnimationEnd(tx, this)
165             );
166             mOneHandedAnimationCallbacks.clear();
167         }
168 
169         @Override
onAnimationCancel(Animator animation)170         public void onAnimationCancel(Animator animation) {
171             mCurrentValue = mEndValue;
172             mOneHandedAnimationCallbacks.forEach(
173                     (callback) -> callback.onOneHandedAnimationCancel(this)
174             );
175             mOneHandedAnimationCallbacks.clear();
176         }
177 
178         @Override
onAnimationRepeat(Animator animation)179         public void onAnimationRepeat(Animator animation) {
180         }
181 
182         @Override
onAnimationUpdate(ValueAnimator animation)183         public void onAnimationUpdate(ValueAnimator animation) {
184             final SurfaceControl.Transaction tx = newSurfaceControlTransaction();
185             mOneHandedAnimationCallbacks.forEach(
186                     (callback) -> callback.onAnimationUpdate(tx, 0f, mCurrentValue)
187             );
188             applySurfaceControlTransaction(mLeash, tx, animation.getAnimatedFraction());
189         }
190 
onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx)191         void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {
192         }
193 
onEndTransaction(SurfaceControl leash, SurfaceControl.Transaction tx)194         void onEndTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {
195         }
196 
applySurfaceControlTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, float fraction)197         abstract void applySurfaceControlTransaction(SurfaceControl leash,
198                 SurfaceControl.Transaction tx, float fraction);
199 
getSurfaceTransactionHelper()200         OneHandedSurfaceTransactionHelper getSurfaceTransactionHelper() {
201             return mSurfaceTransactionHelper;
202         }
203 
setSurfaceTransactionHelper(OneHandedSurfaceTransactionHelper helper)204         void setSurfaceTransactionHelper(OneHandedSurfaceTransactionHelper helper) {
205             mSurfaceTransactionHelper = helper;
206         }
207 
addOneHandedAnimationCallback( @ullable OneHandedAnimationCallback callback)208         OneHandedTransitionAnimator addOneHandedAnimationCallback(
209                 @Nullable OneHandedAnimationCallback callback) {
210             if (callback != null) {
211                 mOneHandedAnimationCallbacks.add(callback);
212             }
213             return this;
214         }
215 
getToken()216         WindowContainerToken getToken() {
217             return mToken;
218         }
219 
getDestinationOffset()220         float getDestinationOffset() {
221             return (mEndValue - mStartValue);
222         }
223 
224         @TransitionDirection
getTransitionDirection()225         int getTransitionDirection() {
226             return mTransitionDirection;
227         }
228 
setTransitionDirection(int direction)229         OneHandedTransitionAnimator setTransitionDirection(int direction) {
230             mTransitionDirection = direction;
231             return this;
232         }
233 
getStartValue()234         float getStartValue() {
235             return mStartValue;
236         }
237 
getEndValue()238         float getEndValue() {
239             return mEndValue;
240         }
241 
setCurrentValue(float value)242         void setCurrentValue(float value) {
243             mCurrentValue = value;
244         }
245 
246         /**
247          * Updates the {@link #mEndValue}.
248          */
updateEndValue(float endValue)249         void updateEndValue(float endValue) {
250             mEndValue = endValue;
251         }
252 
newSurfaceControlTransaction()253         SurfaceControl.Transaction newSurfaceControlTransaction() {
254             return mSurfaceControlTransactionFactory.getTransaction();
255         }
256 
257         @VisibleForTesting
ofYOffset(WindowContainerToken token, SurfaceControl leash, float startValue, float endValue, Rect displayBounds)258         static OneHandedTransitionAnimator ofYOffset(WindowContainerToken token,
259                 SurfaceControl leash, float startValue, float endValue, Rect displayBounds) {
260 
261             return new OneHandedTransitionAnimator(token, leash, startValue, endValue) {
262 
263                 private final Rect mTmpRect = new Rect(displayBounds);
264 
265                 private float getCastedFractionValue(float start, float end, float fraction) {
266                     return (start * (1 - fraction) + end * fraction + .5f);
267                 }
268 
269                 @Override
270                 void applySurfaceControlTransaction(SurfaceControl leash,
271                         SurfaceControl.Transaction tx, float fraction) {
272                     final float start = getStartValue();
273                     final float end = getEndValue();
274                     final float currentValue = getCastedFractionValue(start, end, fraction);
275                     mTmpRect.set(
276                             mTmpRect.left,
277                             mTmpRect.top + Math.round(currentValue),
278                             mTmpRect.right,
279                             mTmpRect.bottom + Math.round(currentValue));
280                     setCurrentValue(currentValue);
281                     getSurfaceTransactionHelper()
282                             .crop(tx, leash, mTmpRect)
283                             .round(tx, leash)
284                             .translate(tx, leash, currentValue);
285                     tx.apply();
286                 }
287 
288                 @Override
289                 void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {
290                     getSurfaceTransactionHelper()
291                             .crop(tx, leash, mTmpRect)
292                             .round(tx, leash)
293                             .translate(tx, leash, getStartValue());
294                     tx.apply();
295                 }
296             };
297         }
298     }
299 
300     /**
301      * An Interpolator for One-Handed transition animation.
302      */
303     public class OneHandedInterpolator extends BaseInterpolator {
304         @Override
getInterpolation(float input)305         public float getInterpolation(float input) {
306             return (float) (Math.pow(2, -10 * input) * Math.sin(((input - 4.0f) / 4.0f)
307                     * (2.0f * Math.PI) / 4.0f) + 1.0f);
308         }
309     }
310 
dump(@onNull PrintWriter pw)311     void dump(@NonNull PrintWriter pw) {
312         final String innerPrefix = "  ";
313         pw.println(TAG + "states: ");
314         pw.print(innerPrefix + "mAnimatorMap=");
315         pw.println(mAnimatorMap);
316 
317         if (mSurfaceTransactionHelper != null) {
318             mSurfaceTransactionHelper.dump(pw);
319         }
320     }
321 }
322