| /* |
| * Copyright (C) 2020 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.wm.shell.pip; |
| |
| import static android.util.RotationUtils.rotateBounds; |
| import static android.view.Surface.ROTATION_270; |
| import static android.view.Surface.ROTATION_90; |
| |
| import android.animation.AnimationHandler; |
| import android.animation.Animator; |
| import android.animation.RectEvaluator; |
| import android.animation.ValueAnimator; |
| import android.annotation.IntDef; |
| import android.annotation.NonNull; |
| import android.app.TaskInfo; |
| import android.content.Context; |
| import android.content.pm.ActivityInfo; |
| import android.graphics.Rect; |
| import android.os.SystemClock; |
| import android.view.Surface; |
| import android.view.SurfaceControl; |
| import android.window.TaskSnapshot; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.graphics.SfVsyncFrameCallbackProvider; |
| import com.android.internal.protolog.common.ProtoLog; |
| import com.android.launcher3.icons.IconProvider; |
| import com.android.wm.shell.animation.Interpolators; |
| import com.android.wm.shell.protolog.ShellProtoLogGroup; |
| import com.android.wm.shell.transition.Transitions; |
| |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.util.Objects; |
| |
| /** |
| * Controller class of PiP animations (both from and to PiP mode). |
| */ |
| public class PipAnimationController { |
| static final float FRACTION_START = 0f; |
| static final float FRACTION_END = 1f; |
| |
| public static final int ANIM_TYPE_BOUNDS = 0; |
| public static final int ANIM_TYPE_ALPHA = 1; |
| |
| @IntDef(prefix = { "ANIM_TYPE_" }, value = { |
| ANIM_TYPE_BOUNDS, |
| ANIM_TYPE_ALPHA |
| }) |
| @Retention(RetentionPolicy.SOURCE) |
| public @interface AnimationType {} |
| |
| /** |
| * The alpha type is set for swiping to home. But the swiped task may not enter PiP. And if |
| * another task enters PiP by non-swipe ways, e.g. call API in foreground or switch to 3-button |
| * navigation, then the alpha type is unexpected. So use a timeout to avoid applying wrong |
| * animation style to an unrelated task. |
| */ |
| private static final int ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS = 800; |
| |
| public static final int TRANSITION_DIRECTION_NONE = 0; |
| public static final int TRANSITION_DIRECTION_SAME = 1; |
| public static final int TRANSITION_DIRECTION_TO_PIP = 2; |
| public static final int TRANSITION_DIRECTION_LEAVE_PIP = 3; |
| public static final int TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN = 4; |
| public static final int TRANSITION_DIRECTION_REMOVE_STACK = 5; |
| public static final int TRANSITION_DIRECTION_SNAP_AFTER_RESIZE = 6; |
| public static final int TRANSITION_DIRECTION_USER_RESIZE = 7; |
| public static final int TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND = 8; |
| |
| @IntDef(prefix = { "TRANSITION_DIRECTION_" }, value = { |
| TRANSITION_DIRECTION_NONE, |
| TRANSITION_DIRECTION_SAME, |
| TRANSITION_DIRECTION_TO_PIP, |
| TRANSITION_DIRECTION_LEAVE_PIP, |
| TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN, |
| TRANSITION_DIRECTION_REMOVE_STACK, |
| TRANSITION_DIRECTION_SNAP_AFTER_RESIZE, |
| TRANSITION_DIRECTION_USER_RESIZE, |
| TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND |
| }) |
| @Retention(RetentionPolicy.SOURCE) |
| public @interface TransitionDirection {} |
| |
| public static boolean isInPipDirection(@TransitionDirection int direction) { |
| return direction == TRANSITION_DIRECTION_TO_PIP; |
| } |
| |
| public static boolean isOutPipDirection(@TransitionDirection int direction) { |
| return direction == TRANSITION_DIRECTION_LEAVE_PIP |
| || direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN; |
| } |
| |
| /** Whether the given direction represents removing PIP. */ |
| public static boolean isRemovePipDirection(@TransitionDirection int direction) { |
| return direction == TRANSITION_DIRECTION_REMOVE_STACK; |
| } |
| |
| private final PipSurfaceTransactionHelper mSurfaceTransactionHelper; |
| |
| private final ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal = |
| ThreadLocal.withInitial(() -> { |
| AnimationHandler handler = new AnimationHandler(); |
| handler.setProvider(new SfVsyncFrameCallbackProvider()); |
| return handler; |
| }); |
| |
| private PipTransitionAnimator mCurrentAnimator; |
| @AnimationType |
| private int mOneShotAnimationType = ANIM_TYPE_BOUNDS; |
| private long mLastOneShotAlphaAnimationTime; |
| |
| public PipAnimationController(PipSurfaceTransactionHelper helper) { |
| mSurfaceTransactionHelper = helper; |
| } |
| |
| @SuppressWarnings("unchecked") |
| @VisibleForTesting |
| public PipTransitionAnimator getAnimator(TaskInfo taskInfo, SurfaceControl leash, |
| Rect destinationBounds, float alphaStart, float alphaEnd) { |
| if (mCurrentAnimator == null) { |
| mCurrentAnimator = setupPipTransitionAnimator( |
| PipTransitionAnimator.ofAlpha(taskInfo, leash, destinationBounds, alphaStart, |
| alphaEnd)); |
| } else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_ALPHA |
| && Objects.equals(destinationBounds, mCurrentAnimator.getDestinationBounds()) |
| && mCurrentAnimator.isRunning()) { |
| mCurrentAnimator.updateEndValue(alphaEnd); |
| } else { |
| mCurrentAnimator.cancel(); |
| mCurrentAnimator = setupPipTransitionAnimator( |
| PipTransitionAnimator.ofAlpha(taskInfo, leash, destinationBounds, alphaStart, |
| alphaEnd)); |
| } |
| return mCurrentAnimator; |
| } |
| |
| @SuppressWarnings("unchecked") |
| /** |
| * Construct and return an animator that animates from the {@param startBounds} to the |
| * {@param endBounds} with the given {@param direction}. If {@param direction} is type |
| * {@link ANIM_TYPE_BOUNDS}, then {@param sourceHintRect} will be used to animate |
| * in a better, more smooth manner. If the original bound was rotated and a reset needs to |
| * happen, pass in {@param startingAngle}. |
| * |
| * In the case where one wants to start animation during an intermediate animation (for example, |
| * if the user is currently doing a pinch-resize, and upon letting go now PiP needs to animate |
| * to the correct snap fraction region), then provide the base bounds, which is current PiP |
| * leash bounds before transformation/any animation. This is so when we try to construct |
| * the different transformation matrices for the animation, we are constructing this based off |
| * the PiP original bounds, rather than the {@param startBounds}, which is post-transformed. |
| * |
| * If non-zero {@param rotationDelta} is given, it means that the display will be rotated by |
| * leaving PiP to fullscreen, and the {@param endBounds} is the fullscreen bounds before the |
| * rotation change. |
| */ |
| @VisibleForTesting |
| public PipTransitionAnimator getAnimator(TaskInfo taskInfo, SurfaceControl leash, |
| Rect baseBounds, Rect startBounds, Rect endBounds, Rect sourceHintRect, |
| @PipAnimationController.TransitionDirection int direction, float startingAngle, |
| @Surface.Rotation int rotationDelta) { |
| if (mCurrentAnimator == null) { |
| mCurrentAnimator = setupPipTransitionAnimator( |
| PipTransitionAnimator.ofBounds(taskInfo, leash, startBounds, startBounds, |
| endBounds, sourceHintRect, direction, 0 /* startingAngle */, |
| rotationDelta)); |
| } else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_ALPHA |
| && mCurrentAnimator.isRunning()) { |
| // If we are still animating the fade into pip, then just move the surface and ensure |
| // we update with the new destination bounds, but don't interrupt the existing animation |
| // with a new bounds |
| mCurrentAnimator.setDestinationBounds(endBounds); |
| } else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_BOUNDS |
| && mCurrentAnimator.isRunning()) { |
| mCurrentAnimator.setDestinationBounds(endBounds); |
| // construct new Rect instances in case they are recycled |
| mCurrentAnimator.updateEndValue(new Rect(endBounds)); |
| } else { |
| mCurrentAnimator.cancel(); |
| mCurrentAnimator = setupPipTransitionAnimator( |
| PipTransitionAnimator.ofBounds(taskInfo, leash, baseBounds, startBounds, |
| endBounds, sourceHintRect, direction, startingAngle, rotationDelta)); |
| } |
| return mCurrentAnimator; |
| } |
| |
| public PipTransitionAnimator getCurrentAnimator() { |
| return mCurrentAnimator; |
| } |
| |
| /** Reset animator state to prevent it from being used after its lifetime. */ |
| public void resetAnimatorState() { |
| mCurrentAnimator = null; |
| } |
| |
| private PipTransitionAnimator setupPipTransitionAnimator(PipTransitionAnimator animator) { |
| animator.setSurfaceTransactionHelper(mSurfaceTransactionHelper); |
| animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); |
| animator.setFloatValues(FRACTION_START, FRACTION_END); |
| animator.setAnimationHandler(mSfAnimationHandlerThreadLocal.get()); |
| return animator; |
| } |
| |
| /** |
| * Returns true if the PiP window is currently being animated. |
| */ |
| public boolean isAnimating() { |
| PipAnimationController.PipTransitionAnimator animator = getCurrentAnimator(); |
| if (animator != null && animator.isRunning()) { |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Quietly cancel the animator by removing the listeners first. |
| */ |
| static void quietCancel(@NonNull ValueAnimator animator) { |
| animator.removeAllUpdateListeners(); |
| animator.removeAllListeners(); |
| animator.cancel(); |
| } |
| |
| /** |
| * Sets the preferred enter animation type for one time. This is typically used to set the |
| * animation type to {@link PipAnimationController#ANIM_TYPE_ALPHA}. |
| * <p> |
| * For example, gesture navigation would first fade out the PiP activity, and the transition |
| * should be responsible to animate in (such as fade in) the PiP. |
| */ |
| public void setOneShotEnterAnimationType(@AnimationType int animationType) { |
| mOneShotAnimationType = animationType; |
| if (animationType == ANIM_TYPE_ALPHA) { |
| mLastOneShotAlphaAnimationTime = SystemClock.uptimeMillis(); |
| } |
| } |
| |
| /** Returns the preferred animation type and consumes the one-shot type if needed. */ |
| @AnimationType |
| public int takeOneShotEnterAnimationType() { |
| final int type = mOneShotAnimationType; |
| if (type == ANIM_TYPE_ALPHA) { |
| // Restore to default type. |
| mOneShotAnimationType = ANIM_TYPE_BOUNDS; |
| if (SystemClock.uptimeMillis() - mLastOneShotAlphaAnimationTime |
| > ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS) { |
| ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, |
| "Alpha animation is expired. Use bounds animation."); |
| return ANIM_TYPE_BOUNDS; |
| } |
| } |
| return type; |
| } |
| |
| /** |
| * Additional callback interface for PiP animation |
| */ |
| public static class PipAnimationCallback { |
| /** |
| * Called when PiP animation is started. |
| */ |
| public void onPipAnimationStart(TaskInfo taskInfo, PipTransitionAnimator animator) {} |
| |
| /** |
| * Called when PiP animation is ended. |
| */ |
| public void onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx, |
| PipTransitionAnimator animator) {} |
| |
| /** |
| * Called when PiP animation is cancelled. |
| */ |
| public void onPipAnimationCancel(TaskInfo taskInfo, PipTransitionAnimator animator) {} |
| } |
| |
| /** |
| * A handler class that could register itself to apply the transaction instead of the |
| * animation controller doing it. For example, the menu controller can be one such handler. |
| */ |
| public static class PipTransactionHandler { |
| |
| /** |
| * Called when the animation controller is about to apply a transaction. Allow a registered |
| * handler to apply the transaction instead. |
| * |
| * @return true if handled by the handler, false otherwise. |
| */ |
| public boolean handlePipTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, |
| Rect destinationBounds, float alpha) { |
| return false; |
| } |
| } |
| |
| /** |
| * Animator for PiP transition animation which supports both alpha and bounds animation. |
| * @param <T> Type of property to animate, either alpha (float) or bounds (Rect) |
| */ |
| public abstract static class PipTransitionAnimator<T> extends ValueAnimator implements |
| ValueAnimator.AnimatorUpdateListener, |
| ValueAnimator.AnimatorListener { |
| private final TaskInfo mTaskInfo; |
| private final SurfaceControl mLeash; |
| private final @AnimationType int mAnimationType; |
| private final Rect mDestinationBounds = new Rect(); |
| |
| private T mBaseValue; |
| protected T mCurrentValue; |
| protected T mStartValue; |
| private T mEndValue; |
| private PipAnimationCallback mPipAnimationCallback; |
| private PipTransactionHandler mPipTransactionHandler; |
| private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory |
| mSurfaceControlTransactionFactory; |
| private PipSurfaceTransactionHelper mSurfaceTransactionHelper; |
| private @TransitionDirection int mTransitionDirection; |
| protected PipContentOverlay mContentOverlay; |
| // Flag to avoid double-end |
| private boolean mHasRequestedEnd; |
| |
| private PipTransitionAnimator(TaskInfo taskInfo, SurfaceControl leash, |
| @AnimationType int animationType, |
| Rect destinationBounds, T baseValue, T startValue, T endValue) { |
| mTaskInfo = taskInfo; |
| mLeash = leash; |
| mAnimationType = animationType; |
| mDestinationBounds.set(destinationBounds); |
| mBaseValue = baseValue; |
| mStartValue = startValue; |
| mEndValue = endValue; |
| addListener(this); |
| addUpdateListener(this); |
| mSurfaceControlTransactionFactory = |
| new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory(); |
| mTransitionDirection = TRANSITION_DIRECTION_NONE; |
| } |
| |
| @Override |
| public void onAnimationStart(Animator animation) { |
| mCurrentValue = mStartValue; |
| onStartTransaction(mLeash, mSurfaceControlTransactionFactory.getTransaction()); |
| if (mPipAnimationCallback != null) { |
| mPipAnimationCallback.onPipAnimationStart(mTaskInfo, this); |
| } |
| } |
| |
| @Override |
| public void onAnimationUpdate(ValueAnimator animation) { |
| if (mHasRequestedEnd) return; |
| applySurfaceControlTransaction(mLeash, |
| mSurfaceControlTransactionFactory.getTransaction(), |
| animation.getAnimatedFraction()); |
| } |
| |
| @Override |
| public void onAnimationEnd(Animator animation) { |
| if (mHasRequestedEnd) return; |
| mHasRequestedEnd = true; |
| mCurrentValue = mEndValue; |
| final SurfaceControl.Transaction tx = |
| mSurfaceControlTransactionFactory.getTransaction(); |
| onEndTransaction(mLeash, tx, mTransitionDirection); |
| if (mPipAnimationCallback != null) { |
| mPipAnimationCallback.onPipAnimationEnd(mTaskInfo, tx, this); |
| } |
| mTransitionDirection = TRANSITION_DIRECTION_NONE; |
| } |
| |
| @Override |
| public void onAnimationCancel(Animator animation) { |
| if (mPipAnimationCallback != null) { |
| mPipAnimationCallback.onPipAnimationCancel(mTaskInfo, this); |
| } |
| mTransitionDirection = TRANSITION_DIRECTION_NONE; |
| } |
| |
| @Override public void onAnimationRepeat(Animator animation) {} |
| |
| @VisibleForTesting |
| @AnimationType public int getAnimationType() { |
| return mAnimationType; |
| } |
| |
| @VisibleForTesting |
| public PipTransitionAnimator<T> setPipAnimationCallback(PipAnimationCallback callback) { |
| mPipAnimationCallback = callback; |
| return this; |
| } |
| |
| PipTransitionAnimator<T> setPipTransactionHandler(PipTransactionHandler handler) { |
| mPipTransactionHandler = handler; |
| return this; |
| } |
| |
| boolean handlePipTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, |
| Rect destinationBounds, float alpha) { |
| if (mPipTransactionHandler != null) { |
| return mPipTransactionHandler.handlePipTransaction( |
| leash, tx, destinationBounds, alpha); |
| } |
| return false; |
| } |
| |
| SurfaceControl getContentOverlayLeash() { |
| return mContentOverlay == null ? null : mContentOverlay.mLeash; |
| } |
| |
| void setColorContentOverlay(Context context) { |
| reattachContentOverlay(new PipContentOverlay.PipColorOverlay(context)); |
| } |
| |
| void setSnapshotContentOverlay(TaskSnapshot snapshot, Rect sourceRectHint) { |
| reattachContentOverlay( |
| new PipContentOverlay.PipSnapshotOverlay(snapshot, sourceRectHint)); |
| } |
| |
| void setAppIconContentOverlay(Context context, Rect bounds, ActivityInfo activityInfo, |
| int appIconSizePx) { |
| reattachContentOverlay( |
| new PipContentOverlay.PipAppIconOverlay(context, bounds, |
| new IconProvider(context).getIcon(activityInfo), appIconSizePx)); |
| } |
| |
| private void reattachContentOverlay(PipContentOverlay overlay) { |
| final SurfaceControl.Transaction tx = |
| mSurfaceControlTransactionFactory.getTransaction(); |
| if (mContentOverlay != null) { |
| mContentOverlay.detach(tx); |
| } |
| mContentOverlay = overlay; |
| mContentOverlay.attach(tx, mLeash); |
| } |
| |
| /** |
| * Clears the {@link #mContentOverlay}, this should be done after the content overlay is |
| * faded out, such as in {@link PipTaskOrganizer#fadeOutAndRemoveOverlay} |
| */ |
| void clearContentOverlay() { |
| mContentOverlay = null; |
| } |
| |
| @VisibleForTesting |
| @TransitionDirection public int getTransitionDirection() { |
| return mTransitionDirection; |
| } |
| |
| @VisibleForTesting |
| public PipTransitionAnimator<T> setTransitionDirection(@TransitionDirection int direction) { |
| if (direction != TRANSITION_DIRECTION_SAME) { |
| mTransitionDirection = direction; |
| } |
| return this; |
| } |
| |
| T getStartValue() { |
| return mStartValue; |
| } |
| |
| T getBaseValue() { |
| return mBaseValue; |
| } |
| |
| @VisibleForTesting |
| public T getEndValue() { |
| return mEndValue; |
| } |
| |
| Rect getDestinationBounds() { |
| return mDestinationBounds; |
| } |
| |
| void setDestinationBounds(Rect destinationBounds) { |
| mDestinationBounds.set(destinationBounds); |
| if (mAnimationType == ANIM_TYPE_ALPHA) { |
| onStartTransaction(mLeash, mSurfaceControlTransactionFactory.getTransaction()); |
| } |
| } |
| |
| void setCurrentValue(T value) { |
| mCurrentValue = value; |
| } |
| |
| boolean shouldApplyCornerRadius() { |
| return !isOutPipDirection(mTransitionDirection); |
| } |
| |
| boolean shouldApplyShadowRadius() { |
| return !isOutPipDirection(mTransitionDirection) |
| && !isRemovePipDirection(mTransitionDirection); |
| } |
| |
| boolean inScaleTransition() { |
| if (mAnimationType != ANIM_TYPE_BOUNDS) return false; |
| final int direction = getTransitionDirection(); |
| return !isInPipDirection(direction) && !isOutPipDirection(direction); |
| } |
| |
| /** |
| * Updates the {@link #mEndValue}. |
| * |
| * NOTE: Do not forget to call {@link #setDestinationBounds(Rect)} for bounds animation. |
| * This is typically used when we receive a shelf height adjustment during the bounds |
| * animation. In which case we can update the end bounds and keep the existing animation |
| * running instead of cancelling it. |
| */ |
| public void updateEndValue(T endValue) { |
| mEndValue = endValue; |
| } |
| |
| @VisibleForTesting |
| public void setSurfaceControlTransactionFactory( |
| PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) { |
| mSurfaceControlTransactionFactory = factory; |
| } |
| |
| PipSurfaceTransactionHelper getSurfaceTransactionHelper() { |
| return mSurfaceTransactionHelper; |
| } |
| |
| void setSurfaceTransactionHelper(PipSurfaceTransactionHelper helper) { |
| mSurfaceTransactionHelper = helper; |
| } |
| |
| void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {} |
| |
| void onEndTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, |
| @TransitionDirection int transitionDirection) {} |
| |
| abstract void applySurfaceControlTransaction(SurfaceControl leash, |
| SurfaceControl.Transaction tx, float fraction); |
| |
| static PipTransitionAnimator<Float> ofAlpha(TaskInfo taskInfo, SurfaceControl leash, |
| Rect destinationBounds, float startValue, float endValue) { |
| return new PipTransitionAnimator<Float>(taskInfo, leash, ANIM_TYPE_ALPHA, |
| destinationBounds, startValue, startValue, endValue) { |
| @Override |
| void applySurfaceControlTransaction(SurfaceControl leash, |
| SurfaceControl.Transaction tx, float fraction) { |
| final float alpha = getStartValue() * (1 - fraction) + getEndValue() * fraction; |
| setCurrentValue(alpha); |
| getSurfaceTransactionHelper().alpha(tx, leash, alpha) |
| .round(tx, leash, shouldApplyCornerRadius()) |
| .shadow(tx, leash, shouldApplyShadowRadius()); |
| if (!handlePipTransaction(leash, tx, destinationBounds, alpha)) { |
| tx.apply(); |
| } |
| } |
| |
| @Override |
| void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) { |
| if (getTransitionDirection() == TRANSITION_DIRECTION_REMOVE_STACK) { |
| // while removing the pip stack, no extra work needs to be done here. |
| return; |
| } |
| getSurfaceTransactionHelper() |
| .resetScale(tx, leash, getDestinationBounds()) |
| .crop(tx, leash, getDestinationBounds()) |
| .round(tx, leash, shouldApplyCornerRadius()) |
| .shadow(tx, leash, shouldApplyShadowRadius()); |
| tx.show(leash); |
| tx.apply(); |
| } |
| |
| @Override |
| public void updateEndValue(Float endValue) { |
| super.updateEndValue(endValue); |
| mStartValue = mCurrentValue; |
| } |
| }; |
| } |
| |
| static PipTransitionAnimator<Rect> ofBounds(TaskInfo taskInfo, SurfaceControl leash, |
| Rect baseValue, Rect startValue, Rect endValue, Rect sourceHintRect, |
| @PipAnimationController.TransitionDirection int direction, float startingAngle, |
| @Surface.Rotation int rotationDelta) { |
| final boolean isOutPipDirection = isOutPipDirection(direction); |
| final boolean isInPipDirection = isInPipDirection(direction); |
| // Just for simplicity we'll interpolate between the source rect hint insets and empty |
| // insets to calculate the window crop |
| final Rect initialSourceValue; |
| if (isOutPipDirection) { |
| initialSourceValue = new Rect(endValue); |
| } else { |
| initialSourceValue = new Rect(baseValue); |
| } |
| |
| final Rect rotatedEndRect; |
| final Rect lastEndRect; |
| final Rect initialContainerRect; |
| if (rotationDelta == ROTATION_90 || rotationDelta == ROTATION_270) { |
| lastEndRect = new Rect(endValue); |
| rotatedEndRect = new Rect(endValue); |
| // Rotate the end bounds according to the rotation delta because the display will |
| // be rotated to the same orientation. |
| rotateBounds(rotatedEndRect, initialSourceValue, rotationDelta); |
| // Use the rect that has the same orientation as the hint rect. |
| initialContainerRect = isOutPipDirection ? rotatedEndRect : initialSourceValue; |
| } else { |
| rotatedEndRect = lastEndRect = null; |
| initialContainerRect = initialSourceValue; |
| } |
| |
| final Rect sourceHintRectInsets; |
| if (sourceHintRect == null) { |
| sourceHintRectInsets = null; |
| } else { |
| sourceHintRectInsets = new Rect(sourceHintRect.left - initialContainerRect.left, |
| sourceHintRect.top - initialContainerRect.top, |
| initialContainerRect.right - sourceHintRect.right, |
| initialContainerRect.bottom - sourceHintRect.bottom); |
| } |
| final Rect zeroInsets = new Rect(0, 0, 0, 0); |
| |
| // construct new Rect instances in case they are recycled |
| return new PipTransitionAnimator<Rect>(taskInfo, leash, ANIM_TYPE_BOUNDS, |
| endValue, new Rect(baseValue), new Rect(startValue), new Rect(endValue)) { |
| private final RectEvaluator mRectEvaluator = new RectEvaluator(new Rect()); |
| private final RectEvaluator mInsetsEvaluator = new RectEvaluator(new Rect()); |
| |
| @Override |
| void applySurfaceControlTransaction(SurfaceControl leash, |
| SurfaceControl.Transaction tx, float fraction) { |
| final Rect base = getBaseValue(); |
| final Rect start = getStartValue(); |
| final Rect end = getEndValue(); |
| Rect bounds = mRectEvaluator.evaluate(fraction, start, end); |
| if (mContentOverlay != null) { |
| mContentOverlay.onAnimationUpdate(tx, bounds, fraction); |
| } |
| if (rotatedEndRect != null) { |
| // Animate the bounds in a different orientation. It only happens when |
| // switching between PiP and fullscreen. |
| applyRotation(tx, leash, fraction, start, end); |
| return; |
| } |
| float angle = (1.0f - fraction) * startingAngle; |
| setCurrentValue(bounds); |
| if (inScaleTransition() || sourceHintRect == null) { |
| if (isOutPipDirection) { |
| getSurfaceTransactionHelper().crop(tx, leash, end) |
| .scale(tx, leash, end, bounds); |
| } else { |
| getSurfaceTransactionHelper().crop(tx, leash, base) |
| .scale(tx, leash, base, bounds, angle) |
| .round(tx, leash, base, bounds) |
| .shadow(tx, leash, shouldApplyShadowRadius()); |
| } |
| } else { |
| final Rect insets = computeInsets(fraction); |
| getSurfaceTransactionHelper().scaleAndCrop(tx, leash, |
| sourceHintRect, initialSourceValue, bounds, insets, |
| isInPipDirection, fraction); |
| if (shouldApplyCornerRadius()) { |
| final Rect sourceBounds = new Rect(initialContainerRect); |
| sourceBounds.inset(insets); |
| getSurfaceTransactionHelper() |
| .round(tx, leash, sourceBounds, bounds) |
| .shadow(tx, leash, shouldApplyShadowRadius()); |
| } |
| } |
| if (!handlePipTransaction(leash, tx, bounds, /* alpha= */ 1f)) { |
| tx.apply(); |
| } |
| } |
| |
| private void applyRotation(SurfaceControl.Transaction tx, SurfaceControl leash, |
| float fraction, Rect start, Rect end) { |
| if (!end.equals(lastEndRect)) { |
| // If the end bounds are changed during animating (e.g. shelf height), the |
| // rotated end bounds also need to be updated. |
| rotatedEndRect.set(endValue); |
| rotateBounds(rotatedEndRect, initialSourceValue, rotationDelta); |
| lastEndRect.set(end); |
| } |
| final Rect bounds = mRectEvaluator.evaluate(fraction, start, rotatedEndRect); |
| setCurrentValue(bounds); |
| final Rect insets = computeInsets(fraction); |
| final float degree, x, y; |
| if (Transitions.SHELL_TRANSITIONS_ROTATION) { |
| if (rotationDelta == ROTATION_90) { |
| degree = 90 * (1 - fraction); |
| x = fraction * (end.left - start.left) |
| + start.left + start.width() * (1 - fraction); |
| y = fraction * (end.top - start.top) + start.top; |
| } else { |
| degree = -90 * (1 - fraction); |
| x = fraction * (end.left - start.left) + start.left; |
| y = fraction * (end.top - start.top) |
| + start.top + start.height() * (1 - fraction); |
| } |
| } else { |
| if (rotationDelta == ROTATION_90) { |
| degree = 90 * fraction; |
| x = fraction * (end.right - start.left) + start.left; |
| y = fraction * (end.top - start.top) + start.top; |
| } else { |
| degree = -90 * fraction; |
| x = fraction * (end.left - start.left) + start.left; |
| y = fraction * (end.bottom - start.top) + start.top; |
| } |
| } |
| final Rect sourceBounds = new Rect(initialContainerRect); |
| sourceBounds.inset(insets); |
| getSurfaceTransactionHelper() |
| .rotateAndScaleWithCrop(tx, leash, initialContainerRect, bounds, |
| insets, degree, x, y, isOutPipDirection, |
| rotationDelta == ROTATION_270 /* clockwise */); |
| if (shouldApplyCornerRadius()) { |
| getSurfaceTransactionHelper() |
| .round(tx, leash, sourceBounds, bounds) |
| .shadow(tx, leash, shouldApplyShadowRadius()); |
| } |
| if (!handlePipTransaction(leash, tx, bounds, 1f /* alpha */)) { |
| tx.apply(); |
| } |
| } |
| |
| private Rect computeInsets(float fraction) { |
| if (sourceHintRectInsets == null) { |
| return zeroInsets; |
| } |
| final Rect startRect = isOutPipDirection ? sourceHintRectInsets : zeroInsets; |
| final Rect endRect = isOutPipDirection ? zeroInsets : sourceHintRectInsets; |
| return mInsetsEvaluator.evaluate(fraction, startRect, endRect); |
| } |
| |
| @Override |
| void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) { |
| getSurfaceTransactionHelper() |
| .alpha(tx, leash, 1f) |
| .round(tx, leash, shouldApplyCornerRadius()) |
| .shadow(tx, leash, shouldApplyShadowRadius()); |
| // TODO(b/178632364): this is a work around for the black background when |
| // entering PiP in button navigation mode. |
| if (isInPipDirection(direction)) { |
| tx.setWindowCrop(leash, getStartValue()); |
| } |
| tx.show(leash); |
| tx.apply(); |
| } |
| |
| @Override |
| void onEndTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, |
| int transitionDirection) { |
| // NOTE: intentionally does not apply the transaction here. |
| // this end transaction should get executed synchronously with the final |
| // WindowContainerTransaction in task organizer |
| final Rect destBounds = getDestinationBounds(); |
| getSurfaceTransactionHelper().resetScale(tx, leash, destBounds); |
| if (isOutPipDirection(transitionDirection)) { |
| // Exit pip, clear scale, position and crop. |
| tx.setMatrix(leash, 1, 0, 0, 1); |
| tx.setPosition(leash, 0, 0); |
| tx.setWindowCrop(leash, 0, 0); |
| } else { |
| getSurfaceTransactionHelper().crop(tx, leash, destBounds); |
| } |
| if (mContentOverlay != null) { |
| mContentOverlay.onAnimationEnd(tx, destBounds); |
| } |
| } |
| |
| @Override |
| public void updateEndValue(Rect endValue) { |
| super.updateEndValue(endValue); |
| if (mStartValue != null && mCurrentValue != null) { |
| mStartValue.set(mCurrentValue); |
| } |
| } |
| }; |
| } |
| } |
| } |