Support edge extension of windows during animations in shell
This is required for the go/t-activity-transition (https://direct.googleplex.com/#/spec/241720001&247800001) which we want to use as the default transition for T Test: atest FlickerTests Bug: 202844659 Change-Id: I316ddfb937953cf462ac999b8d27ae4cb0128847
This commit is contained in:
@@ -950,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
|
||||
@@ -996,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) {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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<Consumer<SurfaceControl.Transaction>> 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<SurfaceControl.Transaction> 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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user