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