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.systemui.pip; 18 19 import android.animation.AnimationHandler; 20 import android.animation.Animator; 21 import android.animation.RectEvaluator; 22 import android.animation.ValueAnimator; 23 import android.annotation.IntDef; 24 import android.content.Context; 25 import android.graphics.Rect; 26 import android.view.SurfaceControl; 27 28 import com.android.internal.annotations.VisibleForTesting; 29 import com.android.internal.graphics.SfVsyncFrameCallbackProvider; 30 import com.android.systemui.Interpolators; 31 32 import java.lang.annotation.Retention; 33 import java.lang.annotation.RetentionPolicy; 34 35 import javax.inject.Inject; 36 37 /** 38 * Controller class of PiP animations (both from and to PiP mode). 39 */ 40 public class PipAnimationController { 41 private static final float FRACTION_START = 0f; 42 private static final float FRACTION_END = 1f; 43 44 public static final int ANIM_TYPE_BOUNDS = 0; 45 public static final int ANIM_TYPE_ALPHA = 1; 46 47 @IntDef(prefix = { "ANIM_TYPE_" }, value = { 48 ANIM_TYPE_BOUNDS, 49 ANIM_TYPE_ALPHA 50 }) 51 @Retention(RetentionPolicy.SOURCE) 52 public @interface AnimationType {} 53 54 public static final int TRANSITION_DIRECTION_NONE = 0; 55 public static final int TRANSITION_DIRECTION_SAME = 1; 56 public static final int TRANSITION_DIRECTION_TO_PIP = 2; 57 public static final int TRANSITION_DIRECTION_TO_FULLSCREEN = 3; 58 public static final int TRANSITION_DIRECTION_TO_SPLIT_SCREEN = 4; 59 60 @IntDef(prefix = { "TRANSITION_DIRECTION_" }, value = { 61 TRANSITION_DIRECTION_NONE, 62 TRANSITION_DIRECTION_SAME, 63 TRANSITION_DIRECTION_TO_PIP, 64 TRANSITION_DIRECTION_TO_FULLSCREEN, 65 TRANSITION_DIRECTION_TO_SPLIT_SCREEN 66 }) 67 @Retention(RetentionPolicy.SOURCE) 68 public @interface TransitionDirection {} 69 isInPipDirection(@ransitionDirection int direction)70 public static boolean isInPipDirection(@TransitionDirection int direction) { 71 return direction == TRANSITION_DIRECTION_TO_PIP; 72 } 73 isOutPipDirection(@ransitionDirection int direction)74 public static boolean isOutPipDirection(@TransitionDirection int direction) { 75 return direction == TRANSITION_DIRECTION_TO_FULLSCREEN 76 || direction == TRANSITION_DIRECTION_TO_SPLIT_SCREEN; 77 } 78 79 private final PipSurfaceTransactionHelper mSurfaceTransactionHelper; 80 81 private PipTransitionAnimator mCurrentAnimator; 82 83 private ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal = 84 ThreadLocal.withInitial(() -> { 85 AnimationHandler handler = new AnimationHandler(); 86 handler.setProvider(new SfVsyncFrameCallbackProvider()); 87 return handler; 88 }); 89 90 @Inject PipAnimationController(Context context, PipSurfaceTransactionHelper helper)91 PipAnimationController(Context context, PipSurfaceTransactionHelper helper) { 92 mSurfaceTransactionHelper = helper; 93 } 94 95 @SuppressWarnings("unchecked") getAnimator(SurfaceControl leash, Rect destinationBounds, float alphaStart, float alphaEnd)96 PipTransitionAnimator getAnimator(SurfaceControl leash, 97 Rect destinationBounds, float alphaStart, float alphaEnd) { 98 if (mCurrentAnimator == null) { 99 mCurrentAnimator = setupPipTransitionAnimator( 100 PipTransitionAnimator.ofAlpha(leash, destinationBounds, alphaStart, alphaEnd)); 101 } else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_ALPHA 102 && mCurrentAnimator.isRunning()) { 103 mCurrentAnimator.updateEndValue(alphaEnd); 104 } else { 105 mCurrentAnimator.cancel(); 106 mCurrentAnimator = setupPipTransitionAnimator( 107 PipTransitionAnimator.ofAlpha(leash, destinationBounds, alphaStart, alphaEnd)); 108 } 109 return mCurrentAnimator; 110 } 111 112 @SuppressWarnings("unchecked") getAnimator(SurfaceControl leash, Rect startBounds, Rect endBounds, Rect sourceHintRect)113 PipTransitionAnimator getAnimator(SurfaceControl leash, Rect startBounds, Rect endBounds, 114 Rect sourceHintRect) { 115 if (mCurrentAnimator == null) { 116 mCurrentAnimator = setupPipTransitionAnimator( 117 PipTransitionAnimator.ofBounds(leash, startBounds, endBounds, sourceHintRect)); 118 } else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_ALPHA 119 && mCurrentAnimator.isRunning()) { 120 // If we are still animating the fade into pip, then just move the surface and ensure 121 // we update with the new destination bounds, but don't interrupt the existing animation 122 // with a new bounds 123 mCurrentAnimator.setDestinationBounds(endBounds); 124 } else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_BOUNDS 125 && mCurrentAnimator.isRunning()) { 126 mCurrentAnimator.setDestinationBounds(endBounds); 127 // construct new Rect instances in case they are recycled 128 mCurrentAnimator.updateEndValue(new Rect(endBounds)); 129 } else { 130 mCurrentAnimator.cancel(); 131 mCurrentAnimator = setupPipTransitionAnimator( 132 PipTransitionAnimator.ofBounds(leash, startBounds, endBounds, sourceHintRect)); 133 } 134 return mCurrentAnimator; 135 } 136 getCurrentAnimator()137 PipTransitionAnimator getCurrentAnimator() { 138 return mCurrentAnimator; 139 } 140 setupPipTransitionAnimator(PipTransitionAnimator animator)141 private PipTransitionAnimator setupPipTransitionAnimator(PipTransitionAnimator animator) { 142 animator.setSurfaceTransactionHelper(mSurfaceTransactionHelper); 143 animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); 144 animator.setFloatValues(FRACTION_START, FRACTION_END); 145 animator.setAnimationHandler(mSfAnimationHandlerThreadLocal.get()); 146 return animator; 147 } 148 149 /** 150 * Additional callback interface for PiP animation 151 */ 152 public static class PipAnimationCallback { 153 /** 154 * Called when PiP animation is started. 155 */ onPipAnimationStart(PipTransitionAnimator animator)156 public void onPipAnimationStart(PipTransitionAnimator animator) {} 157 158 /** 159 * Called when PiP animation is ended. 160 */ onPipAnimationEnd(SurfaceControl.Transaction tx, PipTransitionAnimator animator)161 public void onPipAnimationEnd(SurfaceControl.Transaction tx, 162 PipTransitionAnimator animator) {} 163 164 /** 165 * Called when PiP animation is cancelled. 166 */ onPipAnimationCancel(PipTransitionAnimator animator)167 public void onPipAnimationCancel(PipTransitionAnimator animator) {} 168 } 169 170 /** 171 * Animator for PiP transition animation which supports both alpha and bounds animation. 172 * @param <T> Type of property to animate, either alpha (float) or bounds (Rect) 173 */ 174 public abstract static class PipTransitionAnimator<T> extends ValueAnimator implements 175 ValueAnimator.AnimatorUpdateListener, 176 ValueAnimator.AnimatorListener { 177 private final SurfaceControl mLeash; 178 private final @AnimationType int mAnimationType; 179 private final Rect mDestinationBounds = new Rect(); 180 181 protected T mCurrentValue; 182 protected T mStartValue; 183 private T mEndValue; 184 private PipAnimationCallback mPipAnimationCallback; 185 private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory 186 mSurfaceControlTransactionFactory; 187 private PipSurfaceTransactionHelper mSurfaceTransactionHelper; 188 private @TransitionDirection int mTransitionDirection; 189 PipTransitionAnimator(SurfaceControl leash, @AnimationType int animationType, Rect destinationBounds, T startValue, T endValue)190 private PipTransitionAnimator(SurfaceControl leash, @AnimationType int animationType, 191 Rect destinationBounds, T startValue, T endValue) { 192 mLeash = leash; 193 mAnimationType = animationType; 194 mDestinationBounds.set(destinationBounds); 195 mStartValue = startValue; 196 mEndValue = endValue; 197 addListener(this); 198 addUpdateListener(this); 199 mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new; 200 mTransitionDirection = TRANSITION_DIRECTION_NONE; 201 } 202 203 @Override onAnimationStart(Animator animation)204 public void onAnimationStart(Animator animation) { 205 mCurrentValue = mStartValue; 206 onStartTransaction(mLeash, newSurfaceControlTransaction()); 207 if (mPipAnimationCallback != null) { 208 mPipAnimationCallback.onPipAnimationStart(this); 209 } 210 } 211 212 @Override onAnimationUpdate(ValueAnimator animation)213 public void onAnimationUpdate(ValueAnimator animation) { 214 applySurfaceControlTransaction(mLeash, newSurfaceControlTransaction(), 215 animation.getAnimatedFraction()); 216 } 217 218 @Override onAnimationEnd(Animator animation)219 public void onAnimationEnd(Animator animation) { 220 mCurrentValue = mEndValue; 221 final SurfaceControl.Transaction tx = newSurfaceControlTransaction(); 222 onEndTransaction(mLeash, tx); 223 if (mPipAnimationCallback != null) { 224 mPipAnimationCallback.onPipAnimationEnd(tx, this); 225 } 226 } 227 228 @Override onAnimationCancel(Animator animation)229 public void onAnimationCancel(Animator animation) { 230 if (mPipAnimationCallback != null) { 231 mPipAnimationCallback.onPipAnimationCancel(this); 232 } 233 } 234 onAnimationRepeat(Animator animation)235 @Override public void onAnimationRepeat(Animator animation) {} 236 getAnimationType()237 @AnimationType int getAnimationType() { 238 return mAnimationType; 239 } 240 setPipAnimationCallback(PipAnimationCallback callback)241 PipTransitionAnimator<T> setPipAnimationCallback(PipAnimationCallback callback) { 242 mPipAnimationCallback = callback; 243 return this; 244 } 245 getTransitionDirection()246 @TransitionDirection int getTransitionDirection() { 247 return mTransitionDirection; 248 } 249 setTransitionDirection(@ransitionDirection int direction)250 PipTransitionAnimator<T> setTransitionDirection(@TransitionDirection int direction) { 251 if (direction != TRANSITION_DIRECTION_SAME) { 252 mTransitionDirection = direction; 253 } 254 return this; 255 } 256 getStartValue()257 T getStartValue() { 258 return mStartValue; 259 } 260 getEndValue()261 T getEndValue() { 262 return mEndValue; 263 } 264 getDestinationBounds()265 Rect getDestinationBounds() { 266 return mDestinationBounds; 267 } 268 setDestinationBounds(Rect destinationBounds)269 void setDestinationBounds(Rect destinationBounds) { 270 mDestinationBounds.set(destinationBounds); 271 if (mAnimationType == ANIM_TYPE_ALPHA) { 272 onStartTransaction(mLeash, newSurfaceControlTransaction()); 273 } 274 } 275 setCurrentValue(T value)276 void setCurrentValue(T value) { 277 mCurrentValue = value; 278 } 279 shouldApplyCornerRadius()280 boolean shouldApplyCornerRadius() { 281 return !isOutPipDirection(mTransitionDirection); 282 } 283 inScaleTransition()284 boolean inScaleTransition() { 285 if (mAnimationType != ANIM_TYPE_BOUNDS) return false; 286 return !isInPipDirection(getTransitionDirection()); 287 } 288 289 /** 290 * Updates the {@link #mEndValue}. 291 * 292 * NOTE: Do not forget to call {@link #setDestinationBounds(Rect)} for bounds animation. 293 * This is typically used when we receive a shelf height adjustment during the bounds 294 * animation. In which case we can update the end bounds and keep the existing animation 295 * running instead of cancelling it. 296 */ updateEndValue(T endValue)297 void updateEndValue(T endValue) { 298 mEndValue = endValue; 299 } 300 newSurfaceControlTransaction()301 SurfaceControl.Transaction newSurfaceControlTransaction() { 302 return mSurfaceControlTransactionFactory.getTransaction(); 303 } 304 305 @VisibleForTesting setSurfaceControlTransactionFactory( PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory)306 void setSurfaceControlTransactionFactory( 307 PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) { 308 mSurfaceControlTransactionFactory = factory; 309 } 310 getSurfaceTransactionHelper()311 PipSurfaceTransactionHelper getSurfaceTransactionHelper() { 312 return mSurfaceTransactionHelper; 313 } 314 setSurfaceTransactionHelper(PipSurfaceTransactionHelper helper)315 void setSurfaceTransactionHelper(PipSurfaceTransactionHelper helper) { 316 mSurfaceTransactionHelper = helper; 317 } 318 onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx)319 void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {} 320 onEndTransaction(SurfaceControl leash, SurfaceControl.Transaction tx)321 void onEndTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {} 322 applySurfaceControlTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, float fraction)323 abstract void applySurfaceControlTransaction(SurfaceControl leash, 324 SurfaceControl.Transaction tx, float fraction); 325 ofAlpha(SurfaceControl leash, Rect destinationBounds, float startValue, float endValue)326 static PipTransitionAnimator<Float> ofAlpha(SurfaceControl leash, 327 Rect destinationBounds, float startValue, float endValue) { 328 return new PipTransitionAnimator<Float>(leash, ANIM_TYPE_ALPHA, 329 destinationBounds, startValue, endValue) { 330 @Override 331 void applySurfaceControlTransaction(SurfaceControl leash, 332 SurfaceControl.Transaction tx, float fraction) { 333 final float alpha = getStartValue() * (1 - fraction) + getEndValue() * fraction; 334 setCurrentValue(alpha); 335 getSurfaceTransactionHelper().alpha(tx, leash, alpha); 336 tx.apply(); 337 } 338 339 @Override 340 void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) { 341 getSurfaceTransactionHelper() 342 .resetScale(tx, leash, getDestinationBounds()) 343 .crop(tx, leash, getDestinationBounds()) 344 .round(tx, leash, shouldApplyCornerRadius()); 345 tx.show(leash); 346 tx.apply(); 347 } 348 349 @Override 350 void updateEndValue(Float endValue) { 351 super.updateEndValue(endValue); 352 mStartValue = mCurrentValue; 353 } 354 }; 355 } 356 ofBounds(SurfaceControl leash, Rect startValue, Rect endValue, Rect sourceHintRect)357 static PipTransitionAnimator<Rect> ofBounds(SurfaceControl leash, 358 Rect startValue, Rect endValue, Rect sourceHintRect) { 359 // Just for simplicity we'll interpolate between the source rect hint insets and empty 360 // insets to calculate the window crop 361 final Rect initialStartValue = new Rect(startValue); 362 final Rect sourceHintRectInsets = sourceHintRect != null 363 ? new Rect(sourceHintRect.left - startValue.left, 364 sourceHintRect.top - startValue.top, 365 startValue.right - sourceHintRect.right, 366 startValue.bottom - sourceHintRect.bottom) 367 : null; 368 final Rect sourceInsets = new Rect(0, 0, 0, 0); 369 370 // construct new Rect instances in case they are recycled 371 return new PipTransitionAnimator<Rect>(leash, ANIM_TYPE_BOUNDS, 372 endValue, new Rect(startValue), new Rect(endValue)) { 373 private final RectEvaluator mRectEvaluator = new RectEvaluator(new Rect()); 374 private final RectEvaluator mInsetsEvaluator = new RectEvaluator(new Rect()); 375 376 @Override 377 void applySurfaceControlTransaction(SurfaceControl leash, 378 SurfaceControl.Transaction tx, float fraction) { 379 final Rect start = getStartValue(); 380 final Rect end = getEndValue(); 381 Rect bounds = mRectEvaluator.evaluate(fraction, start, end); 382 setCurrentValue(bounds); 383 if (inScaleTransition()) { 384 if (isOutPipDirection(getTransitionDirection())) { 385 getSurfaceTransactionHelper().scale(tx, leash, end, bounds); 386 } else { 387 getSurfaceTransactionHelper().scale(tx, leash, start, bounds); 388 } 389 } else { 390 if (sourceHintRectInsets != null) { 391 Rect insets = mInsetsEvaluator.evaluate(fraction, sourceInsets, 392 sourceHintRectInsets); 393 getSurfaceTransactionHelper().scaleAndCrop(tx, leash, initialStartValue, 394 bounds, insets); 395 } else { 396 getSurfaceTransactionHelper().scale(tx, leash, start, bounds); 397 } 398 } 399 tx.apply(); 400 } 401 402 @Override 403 void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) { 404 getSurfaceTransactionHelper() 405 .alpha(tx, leash, 1f) 406 .round(tx, leash, shouldApplyCornerRadius()); 407 tx.show(leash); 408 tx.apply(); 409 } 410 411 @Override 412 void onEndTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) { 413 // NOTE: intentionally does not apply the transaction here. 414 // this end transaction should get executed synchronously with the final 415 // WindowContainerTransaction in task organizer 416 getSurfaceTransactionHelper() 417 .resetScale(tx, leash, getDestinationBounds()) 418 .crop(tx, leash, getDestinationBounds()); 419 } 420 421 @Override 422 void updateEndValue(Rect endValue) { 423 super.updateEndValue(endValue); 424 if (mStartValue != null && mCurrentValue != null) { 425 mStartValue.set(mCurrentValue); 426 } 427 } 428 }; 429 } 430 } 431 } 432