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