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