blob: 200af7415eb15ac487d95fb1182086b33a6cc3ee [file] [log] [blame]
/*
* 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.app.TaskInfo;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Rect;
import android.view.Choreographer;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.wm.shell.animation.Interpolators;
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;
private 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 {}
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;
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;
}
PipTransitionAnimator getCurrentAnimator() {
return mCurrentAnimator;
}
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;
}
/**
* 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) {
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 float mStartingAngle;
private PipAnimationCallback mPipAnimationCallback;
private PipTransactionHandler mPipTransactionHandler;
private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
mSurfaceControlTransactionFactory;
private PipSurfaceTransactionHelper mSurfaceTransactionHelper;
private @TransitionDirection int mTransitionDirection;
protected SurfaceControl mContentOverlay;
private PipTransitionAnimator(TaskInfo taskInfo, SurfaceControl leash,
@AnimationType int animationType, Rect destinationBounds, T baseValue, T startValue,
T endValue, float startingAngle) {
mTaskInfo = taskInfo;
mLeash = leash;
mAnimationType = animationType;
mDestinationBounds.set(destinationBounds);
mBaseValue = baseValue;
mStartValue = startValue;
mEndValue = endValue;
mStartingAngle = startingAngle;
addListener(this);
addUpdateListener(this);
mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
mTransitionDirection = TRANSITION_DIRECTION_NONE;
}
@Override
public void onAnimationStart(Animator animation) {
mCurrentValue = mStartValue;
onStartTransaction(mLeash, newSurfaceControlTransaction());
if (mPipAnimationCallback != null) {
mPipAnimationCallback.onPipAnimationStart(mTaskInfo, this);
}
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
applySurfaceControlTransaction(mLeash, newSurfaceControlTransaction(),
animation.getAnimatedFraction());
}
@Override
public void onAnimationEnd(Animator animation) {
mCurrentValue = mEndValue;
final SurfaceControl.Transaction tx = newSurfaceControlTransaction();
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) {
if (mPipTransactionHandler != null) {
return mPipTransactionHandler.handlePipTransaction(leash, tx, destinationBounds);
}
return false;
}
SurfaceControl getContentOverlay() {
return mContentOverlay;
}
PipTransitionAnimator<T> setUseContentOverlay(Context context) {
final SurfaceControl.Transaction tx = newSurfaceControlTransaction();
if (mContentOverlay != null) {
// remove existing content overlay if there is any.
tx.remove(mContentOverlay);
tx.apply();
}
mContentOverlay = new SurfaceControl.Builder(new SurfaceSession())
.setCallsite("PipAnimation")
.setName("PipContentOverlay")
.setColorLayer()
.build();
tx.show(mContentOverlay);
tx.setLayer(mContentOverlay, Integer.MAX_VALUE);
tx.setColor(mContentOverlay, getContentOverlayColor(context));
tx.setAlpha(mContentOverlay, 0f);
tx.reparent(mContentOverlay, mLeash);
tx.apply();
return this;
}
private float[] getContentOverlayColor(Context context) {
final TypedArray ta = context.obtainStyledAttributes(new int[] {
android.R.attr.colorBackground });
try {
int colorAccent = ta.getColor(0, 0);
return new float[] {
Color.red(colorAccent) / 255f,
Color.green(colorAccent) / 255f,
Color.blue(colorAccent) / 255f };
} finally {
ta.recycle();
}
}
/**
* 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, newSurfaceControlTransaction());
}
}
void setCurrentValue(T value) {
mCurrentValue = value;
}
boolean shouldApplyCornerRadius() {
return !isOutPipDirection(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;
}
/**
* @return {@link SurfaceControl.Transaction} instance with vsync-id.
*/
protected SurfaceControl.Transaction newSurfaceControlTransaction() {
final SurfaceControl.Transaction tx =
mSurfaceControlTransactionFactory.getTransaction();
tx.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());
return tx;
}
@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, 0) {
@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());
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());
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);
// 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),
startingAngle) {
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();
if (mContentOverlay != null) {
tx.setAlpha(mContentOverlay, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2);
}
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;
}
Rect bounds = mRectEvaluator.evaluate(fraction, start, end);
float angle = (1.0f - fraction) * startingAngle;
setCurrentValue(bounds);
if (inScaleTransition() || sourceHintRect == null) {
if (isOutPipDirection) {
getSurfaceTransactionHelper().scale(tx, leash, end, bounds);
} else {
getSurfaceTransactionHelper().scale(tx, leash, base, bounds, angle)
.round(tx, leash, base, bounds);
}
} else {
final Rect insets = computeInsets(fraction);
getSurfaceTransactionHelper().scaleAndCrop(tx, leash,
initialSourceValue, bounds, insets);
if (shouldApplyCornerRadius()) {
final Rect destinationBounds = new Rect(bounds);
destinationBounds.inset(insets);
getSurfaceTransactionHelper().round(tx, leash,
initialContainerRect, destinationBounds);
}
}
if (!handlePipTransaction(leash, tx, bounds)) {
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 (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;
}
getSurfaceTransactionHelper()
.rotateAndScaleWithCrop(tx, leash, initialContainerRect, bounds,
insets, degree, x, y, isOutPipDirection,
rotationDelta == ROTATION_270 /* clockwise */)
.round(tx, leash, initialContainerRect, bounds);
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());
// TODO(b/178632364): this is a work around for the black background when
// entering PiP in buttion 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);
}
}
@Override
public void updateEndValue(Rect endValue) {
super.updateEndValue(endValue);
if (mStartValue != null && mCurrentValue != null) {
mStartValue.set(mCurrentValue);
}
}
};
}
}
}