diff --git a/core/api/current.txt b/core/api/current.txt index e2325b05b5c81..c1d692f4b3a66 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -729,6 +729,10 @@ package android { field public static final int freezesText = 16843116; // 0x101016c field public static final int fromAlpha = 16843210; // 0x10101ca field public static final int fromDegrees = 16843187; // 0x10101b3 + field public static final int fromExtendBottom; + field public static final int fromExtendLeft; + field public static final int fromExtendRight; + field public static final int fromExtendTop; field public static final int fromId = 16843850; // 0x101044a field public static final int fromScene = 16843741; // 0x10103dd field public static final int fromXDelta = 16843206; // 0x10101c6 @@ -1574,6 +1578,10 @@ package android { field public static final int titleTextStyle = 16843512; // 0x10102f8 field public static final int toAlpha = 16843211; // 0x10101cb field public static final int toDegrees = 16843188; // 0x10101b4 + field public static final int toExtendBottom; + field public static final int toExtendLeft; + field public static final int toExtendRight; + field public static final int toExtendTop; field public static final int toId = 16843849; // 0x1010449 field public static final int toScene = 16843742; // 0x10103de field public static final int toXDelta = 16843207; // 0x10101c7 diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java index ab749ee284a86..3914a3c963b6a 100644 --- a/core/java/android/view/animation/Animation.java +++ b/core/java/android/view/animation/Animation.java @@ -864,6 +864,15 @@ public abstract class Animation implements Cloneable { return mHasRoundedCorners; } + /** + * @return if a window animation has outsets applied to it. + * + * @hide + */ + public boolean hasExtension() { + return false; + } + /** * If showBackground is {@code true} and this animation is applied on a window, then the windows * in the animation will animate with the background associated with this window behind them. @@ -941,6 +950,21 @@ public abstract class Animation implements Cloneable { return (getStartOffset() + getDuration()) * (getRepeatCount() + 1); } + /** + * Gets the transformation to apply a specific point in time. Implementations of this method + * should always be kept in sync with getTransformation. + * + * @param normalizedTime time between 0 and 1 where 0 is the start of the animation and 1 the + * end. + * @param outTransformation A transformation object that is provided by the + * caller and will be filled in by the animation. + * @hide + */ + public void getTransformationAt(float normalizedTime, Transformation outTransformation) { + final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime); + applyTransformation(interpolatedTime, outTransformation); + } + /** * Gets the transformation to apply at a specified point in time. Implementations of this * method should always replace the specified Transformation or document they are doing @@ -987,8 +1011,7 @@ public abstract class Animation implements Cloneable { normalizedTime = 1.0f - normalizedTime; } - final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime); - applyTransformation(interpolatedTime, outTransformation); + getTransformationAt(normalizedTime, outTransformation); } if (expired) { diff --git a/core/java/android/view/animation/AnimationSet.java b/core/java/android/view/animation/AnimationSet.java index 03c6ca69a592c..a2f3544c70ab3 100644 --- a/core/java/android/view/animation/AnimationSet.java +++ b/core/java/android/view/animation/AnimationSet.java @@ -359,6 +359,26 @@ public class AnimationSet extends Animation { } } + /** + * The transformation of an animation set is the concatenation of all of its + * component animations. + * + * @see android.view.animation.Animation#getTransformationAt + * @hide + */ + @Override + public void getTransformationAt(float interpolatedTime, Transformation t) { + final Transformation temp = mTempTransformation; + + for (int i = mAnimations.size() - 1; i >= 0; --i) { + final Animation a = mAnimations.get(i); + + temp.clear(); + a.getTransformationAt(interpolatedTime, t); + t.compose(temp); + } + } + /** * The transformation of an animation set is the concatenation of all of its * component animations. @@ -517,4 +537,15 @@ public class AnimationSet extends Animation { public boolean willChangeBounds() { return (mFlags & PROPERTY_CHANGE_BOUNDS_MASK) == PROPERTY_CHANGE_BOUNDS_MASK; } + + /** @hide */ + @Override + public boolean hasExtension() { + for (Animation animation : mAnimations) { + if (animation.hasExtension()) { + return true; + } + } + return false; + } } diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java index 7ce0f4571f762..7d1dc76608718 100644 --- a/core/java/android/view/animation/AnimationUtils.java +++ b/core/java/android/view/animation/AnimationUtils.java @@ -190,6 +190,8 @@ public class AnimationUtils { anim = new TranslateAnimation(c, attrs); } else if (name.equals("cliprect")) { anim = new ClipRectAnimation(c, attrs); + } else if (name.equals("extend")) { + anim = new ExtendAnimation(c, attrs); } else { throw new RuntimeException("Unknown animation name: " + parser.getName()); } diff --git a/core/java/android/view/animation/ExtendAnimation.java b/core/java/android/view/animation/ExtendAnimation.java new file mode 100644 index 0000000000000..fd627e50ab0e5 --- /dev/null +++ b/core/java/android/view/animation/ExtendAnimation.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2022 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 android.view.animation; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Insets; +import android.util.AttributeSet; + +/** + * An animation that controls the outset of an object. + * + * @hide + */ +public class ExtendAnimation extends Animation { + protected Insets mFromInsets = Insets.NONE; + protected Insets mToInsets = Insets.NONE; + + private int mFromLeftType = ABSOLUTE; + private int mFromTopType = ABSOLUTE; + private int mFromRightType = ABSOLUTE; + private int mFromBottomType = ABSOLUTE; + + private int mToLeftType = ABSOLUTE; + private int mToTopType = ABSOLUTE; + private int mToRightType = ABSOLUTE; + private int mToBottomType = ABSOLUTE; + + private float mFromLeftValue; + private float mFromTopValue; + private float mFromRightValue; + private float mFromBottomValue; + + private float mToLeftValue; + private float mToTopValue; + private float mToRightValue; + private float mToBottomValue; + + /** + * Constructor used when an ExtendAnimation is loaded from a resource. + * + * @param context Application context to use + * @param attrs Attribute set from which to read values + */ + public ExtendAnimation(Context context, AttributeSet attrs) { + super(context, attrs); + + TypedArray a = context.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.ExtendAnimation); + + Description d = Description.parseValue(a.peekValue( + com.android.internal.R.styleable.ExtendAnimation_fromExtendLeft)); + mFromLeftType = d.type; + mFromLeftValue = d.value; + + d = Description.parseValue(a.peekValue( + com.android.internal.R.styleable.ExtendAnimation_fromExtendTop)); + mFromTopType = d.type; + mFromTopValue = d.value; + + d = Description.parseValue(a.peekValue( + com.android.internal.R.styleable.ExtendAnimation_fromExtendRight)); + mFromRightType = d.type; + mFromRightValue = d.value; + + d = Description.parseValue(a.peekValue( + com.android.internal.R.styleable.ExtendAnimation_fromExtendBottom)); + mFromBottomType = d.type; + mFromBottomValue = d.value; + + + d = Description.parseValue(a.peekValue( + com.android.internal.R.styleable.ExtendAnimation_toExtendLeft)); + mToLeftType = d.type; + mToLeftValue = d.value; + + d = Description.parseValue(a.peekValue( + com.android.internal.R.styleable.ExtendAnimation_toExtendTop)); + mToTopType = d.type; + mToTopValue = d.value; + + d = Description.parseValue(a.peekValue( + com.android.internal.R.styleable.ExtendAnimation_toExtendRight)); + mToRightType = d.type; + mToRightValue = d.value; + + d = Description.parseValue(a.peekValue( + com.android.internal.R.styleable.ExtendAnimation_toExtendBottom)); + mToBottomType = d.type; + mToBottomValue = d.value; + + a.recycle(); + } + + /** + * Constructor to use when building an ExtendAnimation from code + * + * @param fromInsets the insets to animate from + * @param toInsets the insets to animate to + */ + public ExtendAnimation(Insets fromInsets, Insets toInsets) { + if (fromInsets == null || toInsets == null) { + throw new RuntimeException("Expected non-null animation outsets"); + } + mFromLeftValue = -fromInsets.left; + mFromTopValue = -fromInsets.top; + mFromRightValue = -fromInsets.right; + mFromBottomValue = -fromInsets.bottom; + + mToLeftValue = -toInsets.left; + mToTopValue = -toInsets.top; + mToRightValue = -toInsets.right; + mToBottomValue = -toInsets.bottom; + } + + /** + * Constructor to use when building an ExtendAnimation from code + */ + public ExtendAnimation(int fromL, int fromT, int fromR, int fromB, + int toL, int toT, int toR, int toB) { + this(Insets.of(-fromL, -fromT, -fromR, -fromB), Insets.of(-toL, -toT, -toR, -toB)); + } + + @Override + protected void applyTransformation(float it, Transformation tr) { + int l = mFromInsets.left + (int) ((mToInsets.left - mFromInsets.left) * it); + int t = mFromInsets.top + (int) ((mToInsets.top - mFromInsets.top) * it); + int r = mFromInsets.right + (int) ((mToInsets.right - mFromInsets.right) * it); + int b = mFromInsets.bottom + (int) ((mToInsets.bottom - mFromInsets.bottom) * it); + tr.setInsets(l, t, r, b); + } + + @Override + public boolean willChangeTransformationMatrix() { + return false; + } + + /** @hide */ + @Override + public boolean hasExtension() { + return mFromInsets.left < 0 || mFromInsets.top < 0 || mFromInsets.right < 0 + || mFromInsets.bottom < 0; + } + + @Override + public void initialize(int width, int height, int parentWidth, int parentHeight) { + super.initialize(width, height, parentWidth, parentHeight); + // We remove any negative extension (i.e. positive insets) and set those to 0 + mFromInsets = Insets.min(Insets.of( + -(int) resolveSize(mFromLeftType, mFromLeftValue, width, parentWidth), + -(int) resolveSize(mFromTopType, mFromTopValue, height, parentHeight), + -(int) resolveSize(mFromRightType, mFromRightValue, width, parentWidth), + -(int) resolveSize(mFromBottomType, mFromBottomValue, height, parentHeight) + ), Insets.NONE); + mToInsets = Insets.min(Insets.of( + -(int) resolveSize(mToLeftType, mToLeftValue, width, parentWidth), + -(int) resolveSize(mToTopType, mToTopValue, height, parentHeight), + -(int) resolveSize(mToRightType, mToRightValue, width, parentWidth), + -(int) resolveSize(mToBottomType, mToBottomValue, height, parentHeight) + ), Insets.NONE); + } +} diff --git a/core/java/android/view/animation/Transformation.java b/core/java/android/view/animation/Transformation.java index b35a66e6eb26b..bd623088017a3 100644 --- a/core/java/android/view/animation/Transformation.java +++ b/core/java/android/view/animation/Transformation.java @@ -18,6 +18,7 @@ package android.view.animation; import android.annotation.FloatRange; import android.compat.annotation.UnsupportedAppUsage; +import android.graphics.Insets; import android.graphics.Matrix; import android.graphics.Rect; @@ -53,6 +54,8 @@ public class Transformation { private boolean mHasClipRect; private Rect mClipRect = new Rect(); + private Insets mInsets = Insets.NONE; + /** * Creates a new transformation with alpha = 1 and the identity matrix. */ @@ -132,8 +135,9 @@ public class Transformation { setClipRect(bounds); } } + setInsets(Insets.add(getInsets(), t.getInsets())); } - + /** * Like {@link #compose(Transformation)} but does this.postConcat(t) of * the transformation matrix. @@ -160,7 +164,7 @@ public class Transformation { public Matrix getMatrix() { return mMatrix; } - + /** * Sets the degree of transparency * @param alpha 1.0 means fully opaqe and 0.0 means fully transparent @@ -169,6 +173,13 @@ public class Transformation { mAlpha = alpha; } + /** + * @return The degree of transparency + */ + public float getAlpha() { + return mAlpha; + } + /** * Sets the current Transform's clip rect * @hide @@ -203,12 +214,29 @@ public class Transformation { } /** - * @return The degree of transparency + * Sets the current Transform's insets + * @hide */ - public float getAlpha() { - return mAlpha; + public void setInsets(Insets insets) { + mInsets = insets; } - + + /** + * Sets the current Transform's insets + * @hide + */ + public void setInsets(int left, int top, int right, int bottom) { + mInsets = Insets.of(left, top, right, bottom); + } + + /** + * Returns the current Transform's outset rect + * @hide + */ + public Insets getInsets() { + return mInsets; + } + @Override public String toString() { StringBuilder sb = new StringBuilder(64); @@ -216,7 +244,7 @@ public class Transformation { toShortString(sb); return sb.toString(); } - + /** * Return a string representation of the transformation in a compact form. */ @@ -225,7 +253,7 @@ public class Transformation { toShortString(sb); return sb.toString(); } - + /** * @hide */ @@ -234,7 +262,7 @@ public class Transformation { sb.append(" matrix="); sb.append(mMatrix.toShortString()); sb.append('}'); } - + /** * Print short string, to optimize dumping. * @hide diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 7916ef43da509..04e29890568a9 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -2885,7 +2885,7 @@ public void sayHello(View v) method of your context (typically, your Activity). {@deprecated View actually traverses the Context - hierarchy looking for the relevant method, which is fragile (an intermediate + hierarchy looking for the relevant method, which is fragile (an intermediate ContextWrapper adding a same-named method would change behavior) and restricts bytecode optimizers such as R8. Instead, use View.setOnClickListener.}--> @@ -6975,6 +6975,34 @@ + + + + + + + + + + + + + + + + + + + + @@ -7827,7 +7855,7 @@ diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 2e96c65ef17ff..047c04b3596c7 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3260,6 +3260,14 @@ + + + + + + + + diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index 27b6dc5b304f0..ddf01a8c5ee95 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -67,7 +67,11 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.Insets; +import android.graphics.Paint; +import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.Drawable; @@ -78,6 +82,7 @@ import android.os.SystemProperties; import android.os.UserHandle; import android.util.ArrayMap; import android.view.Choreographer; +import android.view.Surface; import android.view.SurfaceControl; import android.view.SurfaceSession; import android.view.WindowManager; @@ -103,6 +108,8 @@ import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.protolog.ShellProtoLogGroup; import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; /** The default handler that handles anything not already handled. */ public class DefaultTransitionHandler implements Transitions.TransitionHandler { @@ -331,6 +338,9 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); }; + final List> postStartTransactionCallbacks = + new ArrayList<>(); + @ColorInt int backgroundColorForTransition = 0; final int wallpaperTransit = getWallpaperTransitType(info); for (int i = info.getChanges().size() - 1; i >= 0; --i) { @@ -402,13 +412,15 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } } - float cornerRadius = 0; + final float cornerRadius; if (a.hasRoundedCorners() && isTask) { // hasRoundedCorners is currently only enabled for tasks final Context displayContext = mDisplayController.getDisplayContext(change.getTaskInfo().displayId); cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(displayContext); + } else { + cornerRadius = 0; } if (a.getShowBackground()) { @@ -424,12 +436,37 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } } + boolean delayedEdgeExtension = false; + if (!isTask && a.hasExtension()) { + if (!Transitions.isOpeningType(change.getMode())) { + // Can screenshot now (before startTransaction is applied) + edgeExtendWindow(change, a, startTransaction, finishTransaction); + } else { + // Need to screenshot after startTransaction is applied otherwise activity + // may not be visible or ready yet. + postStartTransactionCallbacks + .add(t -> edgeExtendWindow(change, a, t, finishTransaction)); + delayedEdgeExtension = true; + } + } + final Rect clipRect = Transitions.isClosingType(change.getMode()) ? mRotator.getEndBoundsInStartRotation(change) : change.getEndAbsBounds(); - startSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish, - mTransactionPool, mMainExecutor, mAnimExecutor, null /* position */, - cornerRadius, clipRect); + + if (delayedEdgeExtension) { + // If the edge extension needs to happen after the startTransition has been + // applied, then we want to only start the animation after the edge extension + // postStartTransaction callback has been run + postStartTransactionCallbacks.add(t -> + startSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish, + mTransactionPool, mMainExecutor, mAnimExecutor, + null /* position */, cornerRadius, clipRect)); + } else { + startSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish, + mTransactionPool, mMainExecutor, mAnimExecutor, null /* position */, + cornerRadius, clipRect); + } if (info.getAnimationOptions() != null) { attachThumbnail(animations, onAnimFinish, change, info.getAnimationOptions(), @@ -443,7 +480,20 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { startTransaction, finishTransaction); } - startTransaction.apply(); + // postStartTransactionCallbacks require that the start transaction is already + // applied to run otherwise they may result in flickers and UI inconsistencies. + boolean waitForStartTransactionApply = postStartTransactionCallbacks.size() > 0; + startTransaction.apply(waitForStartTransactionApply); + + // Run tasks that require startTransaction to already be applied + for (Consumer postStartTransactionCallback : + postStartTransactionCallbacks) { + final SurfaceControl.Transaction t = mTransactionPool.acquire(); + postStartTransactionCallback.accept(t); + t.apply(); + mTransactionPool.release(t); + } + mRotator.cleanUp(finishTransaction); TransitionMetrics.getInstance().reportAnimationStart(transition); // run finish now in-case there are no animations @@ -451,6 +501,117 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { return true; } + private void edgeExtendWindow(TransitionInfo.Change change, + Animation a, SurfaceControl.Transaction startTransaction, + SurfaceControl.Transaction finishTransaction) { + final Transformation transformationAtStart = new Transformation(); + a.getTransformationAt(0, transformationAtStart); + final Transformation transformationAtEnd = new Transformation(); + a.getTransformationAt(1, transformationAtEnd); + + // We want to create an extension surface that is the maximal size and the animation will + // take care of cropping any part that overflows. + final Insets maxExtensionInsets = Insets.min( + transformationAtStart.getInsets(), transformationAtEnd.getInsets()); + + final int targetSurfaceHeight = Math.max(change.getStartAbsBounds().height(), + change.getEndAbsBounds().height()); + final int targetSurfaceWidth = Math.max(change.getStartAbsBounds().width(), + change.getEndAbsBounds().width()); + if (maxExtensionInsets.left < 0) { + final Rect edgeBounds = new Rect(0, 0, 1, targetSurfaceHeight); + final Rect extensionRect = new Rect(0, 0, + -maxExtensionInsets.left, targetSurfaceHeight); + final int xPos = maxExtensionInsets.left; + final int yPos = 0; + createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos, + "Left Edge Extension", startTransaction, finishTransaction); + } + + if (maxExtensionInsets.top < 0) { + final Rect edgeBounds = new Rect(0, 0, targetSurfaceWidth, 1); + final Rect extensionRect = new Rect(0, 0, + targetSurfaceWidth, -maxExtensionInsets.top); + final int xPos = 0; + final int yPos = maxExtensionInsets.top; + createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos, + "Top Edge Extension", startTransaction, finishTransaction); + } + + if (maxExtensionInsets.right < 0) { + final Rect edgeBounds = new Rect(targetSurfaceWidth - 1, 0, + targetSurfaceWidth, targetSurfaceHeight); + final Rect extensionRect = new Rect(0, 0, + -maxExtensionInsets.right, targetSurfaceHeight); + final int xPos = targetSurfaceWidth; + final int yPos = 0; + createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos, + "Right Edge Extension", startTransaction, finishTransaction); + } + + if (maxExtensionInsets.bottom < 0) { + final Rect edgeBounds = new Rect(0, targetSurfaceHeight - 1, + targetSurfaceWidth, targetSurfaceHeight); + final Rect extensionRect = new Rect(0, 0, + targetSurfaceWidth, -maxExtensionInsets.bottom); + final int xPos = maxExtensionInsets.left; + final int yPos = targetSurfaceHeight; + createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos, + "Bottom Edge Extension", startTransaction, finishTransaction); + } + } + + private SurfaceControl createExtensionSurface(SurfaceControl surfaceToExtend, Rect edgeBounds, + Rect extensionRect, int xPos, int yPos, String layerName, + SurfaceControl.Transaction startTransaction, + SurfaceControl.Transaction finishTransaction) { + final SurfaceControl edgeExtensionLayer = new SurfaceControl.Builder() + .setName(layerName) + .setParent(surfaceToExtend) + .setHidden(true) + .setCallsite("DefaultTransitionHandler#startAnimation") + .setOpaque(true) + .setBufferSize(extensionRect.width(), extensionRect.height()) + .build(); + + SurfaceControl.LayerCaptureArgs captureArgs = + new SurfaceControl.LayerCaptureArgs.Builder(surfaceToExtend) + .setSourceCrop(edgeBounds) + .setFrameScale(1) + .setPixelFormat(PixelFormat.RGBA_8888) + .setChildrenOnly(true) + .setAllowProtected(true) + .build(); + final SurfaceControl.ScreenshotHardwareBuffer edgeBuffer = + SurfaceControl.captureLayers(captureArgs); + + if (edgeBuffer == null) { + ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, + "Failed to capture edge of window."); + return null; + } + + android.graphics.BitmapShader shader = + new android.graphics.BitmapShader(edgeBuffer.asBitmap(), + android.graphics.Shader.TileMode.CLAMP, + android.graphics.Shader.TileMode.CLAMP); + final Paint paint = new Paint(); + paint.setShader(shader); + + final Surface surface = new Surface(edgeExtensionLayer); + Canvas c = surface.lockHardwareCanvas(); + c.drawRect(extensionRect, paint); + surface.unlockCanvasAndPost(c); + surface.release(); + + startTransaction.setLayer(edgeExtensionLayer, Integer.MIN_VALUE); + startTransaction.setPosition(edgeExtensionLayer, xPos, yPos); + startTransaction.setVisibility(edgeExtensionLayer, true); + finishTransaction.remove(edgeExtensionLayer); + + return edgeExtensionLayer; + } + private void addBackgroundToTransition( @NonNull SurfaceControl rootLeash, @ColorInt int color, @@ -778,9 +939,17 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } t.setMatrix(leash, transformation.getMatrix(), matrix); t.setAlpha(leash, transformation.getAlpha()); + + Insets extensionInsets = Insets.min(transformation.getInsets(), Insets.NONE); + if (!extensionInsets.equals(Insets.NONE) && clipRect != null && !clipRect.isEmpty()) { + // Clip out any overflowing edge extension + clipRect.inset(extensionInsets); + t.setCrop(leash, clipRect); + } + if (anim.hasRoundedCorners() && cornerRadius > 0 && clipRect != null) { // We can only apply rounded corner if a crop is set - t.setWindowCrop(leash, clipRect); + t.setCrop(leash, clipRect); t.setCornerRadius(leash, cornerRadius); }