Backport window extension animation support

Support was only added to shell, adding support in the legacy transition system

Test: atest CtsWindowManagerDeviceTestCases:AnimationEdgeExtensionTests

Bug: 218687242
Change-Id: Ie423167f62c6f56b7b3c1d949de7774c5fa70b6f
This commit is contained in:
Pablo Gamito
2022-02-10 13:31:51 +01:00
parent cde55d305f
commit 2a8bcd96f5
3 changed files with 224 additions and 8 deletions

View File

@@ -174,5 +174,9 @@ class LocalAnimationAdapter implements AnimationAdapter {
}
void dumpDebugInner(ProtoOutputStream proto);
default WindowAnimationSpec asWindowAnimationSpec() {
return null;
}
}
}

View File

@@ -26,13 +26,22 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.Nullable;
import android.graphics.Canvas;
import android.graphics.Insets;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.hardware.power.Boost;
import android.os.Handler;
import android.os.PowerManagerInternal;
import android.util.ArrayMap;
import android.util.Log;
import android.view.Choreographer;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -40,6 +49,8 @@ import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.server.AnimationThread;
import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
/**
@@ -71,6 +82,10 @@ class SurfaceAnimationRunner {
@VisibleForTesting
final ArrayMap<SurfaceControl, RunningAnimation> mPendingAnimations = new ArrayMap<>();
@GuardedBy("mLock")
@VisibleForTesting
final ArrayMap<SurfaceControl, RunningAnimation> mPreProcessingAnimations = new ArrayMap<>();
@GuardedBy("mLock")
@VisibleForTesting
final ArrayMap<SurfaceControl, RunningAnimation> mRunningAnimations = new ArrayMap<>();
@@ -136,23 +151,64 @@ class SurfaceAnimationRunner {
synchronized (mLock) {
final RunningAnimation runningAnim = new RunningAnimation(a, animationLeash,
finishCallback);
mPendingAnimations.put(animationLeash, runningAnim);
if (!mAnimationStartDeferred) {
mChoreographer.postFrameCallback(this::startAnimations);
boolean requiresEdgeExtension = requiresEdgeExtension(a);
if (requiresEdgeExtension) {
mPreProcessingAnimations.put(animationLeash, runningAnim);
// We must wait for t to be committed since otherwise the leash doesn't have the
// windows we want to screenshot and extend as children.
t.addTransactionCommittedListener(Runnable::run, () -> {
final WindowAnimationSpec animationSpec = a.asWindowAnimationSpec();
final Runnable cleanUpEdgeExtension = edgeExtendWindow(animationLeash,
animationSpec.getRootTaskBounds(), animationSpec.getAnimation(),
mFrameTransaction);
runningAnim.mFinishCallback = () -> {
cleanUpEdgeExtension.run();
finishCallback.run();
};
synchronized (mLock) {
// only run if animation is not yet canceled by this point
if (mPreProcessingAnimations.get(animationLeash) == runningAnim) {
mPreProcessingAnimations.remove(animationLeash);
mPendingAnimations.put(animationLeash, runningAnim);
if (!mAnimationStartDeferred) {
mChoreographer.postFrameCallback(this::startAnimations);
}
}
}
});
}
// Some animations (e.g. move animations) require the initial transform to be applied
// immediately.
applyTransformation(runningAnim, t, 0 /* currentPlayTime */);
if (!requiresEdgeExtension) {
mPendingAnimations.put(animationLeash, runningAnim);
if (!mAnimationStartDeferred) {
mChoreographer.postFrameCallback(this::startAnimations);
}
// Some animations (e.g. move animations) require the initial transform to be
// applied immediately.
applyTransformation(runningAnim, t, 0 /* currentPlayTime */);
}
}
}
private boolean requiresEdgeExtension(AnimationSpec a) {
return a.asWindowAnimationSpec() != null && a.asWindowAnimationSpec().hasExtension();
}
void onAnimationCancelled(SurfaceControl leash) {
synchronized (mLock) {
if (mPendingAnimations.containsKey(leash)) {
mPendingAnimations.remove(leash);
return;
}
if (mPreProcessingAnimations.containsKey(leash)) {
mPreProcessingAnimations.remove(leash);
return;
}
final RunningAnimation anim = mRunningAnimations.get(leash);
if (anim != null) {
mRunningAnimations.remove(leash);
@@ -264,10 +320,133 @@ class SurfaceAnimationRunner {
mApplyScheduled = false;
}
private Runnable edgeExtendWindow(SurfaceControl leash, Rect bounds, Animation a,
Transaction transaction) {
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 = bounds.height();
final int targetSurfaceWidth = bounds.width();
final List<SurfaceControl> extensionSurfaces = new ArrayList<>();
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;
final SurfaceControl extensionSurface = createExtensionSurface(leash, edgeBounds,
extensionRect, xPos, yPos, "Left Edge Extension", transaction);
extensionSurfaces.add(extensionSurface);
}
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;
final SurfaceControl extensionSurface = createExtensionSurface(leash, edgeBounds,
extensionRect, xPos, yPos, "Top Edge Extension", transaction);
extensionSurfaces.add(extensionSurface);
}
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;
final SurfaceControl extensionSurface = createExtensionSurface(leash, edgeBounds,
extensionRect, xPos, yPos, "Right Edge Extension", transaction);
extensionSurfaces.add(extensionSurface);
}
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;
final SurfaceControl extensionSurface = createExtensionSurface(leash, edgeBounds,
extensionRect, xPos, yPos, "Bottom Edge Extension", transaction);
extensionSurfaces.add(extensionSurface);
}
Runnable cleanUp = () -> {
for (final SurfaceControl extensionSurface : extensionSurfaces) {
if (extensionSurface != null) {
transaction.remove(extensionSurface);
}
}
};
return cleanUp;
}
private SurfaceControl createExtensionSurface(SurfaceControl surfaceToExtend, Rect edgeBounds,
Rect extensionRect, int xPos, int yPos, String layerName,
Transaction startTransaction) {
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) {
Log.e("SurfaceAnimationRunner", "Failed to create edge extension - "
+ "edge buffer is null");
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);
return edgeExtensionLayer;
}
private static final class RunningAnimation {
final AnimationSpec mAnimSpec;
final SurfaceControl mLeash;
final Runnable mFinishCallback;
Runnable mFinishCallback;
ValueAnimator mAnim;
@GuardedBy("mCancelLock")

View File

@@ -21,6 +21,7 @@ import static com.android.server.wm.AnimationSpecProto.WINDOW;
import static com.android.server.wm.WindowAnimationSpecProto.ANIMATION;
import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_NONE;
import android.graphics.Insets;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.SystemClock;
@@ -74,6 +75,11 @@ public class WindowAnimationSpec implements AnimationSpec {
}
}
@Override
public WindowAnimationSpec asWindowAnimationSpec() {
return this;
}
@Override
public boolean getShowWallpaper() {
return mAnimation.getShowWallpaper();
@@ -89,11 +95,27 @@ public class WindowAnimationSpec implements AnimationSpec {
return mAnimation.getBackgroundColor();
}
/**
* @return If a window animation has outsets applied to it.
* @see Animation#hasExtension()
*/
public boolean hasExtension() {
return mAnimation.hasExtension();
}
@Override
public long getDuration() {
return mAnimation.computeDurationHint();
}
public Rect getRootTaskBounds() {
return mRootTaskBounds;
}
public Animation getAnimation() {
return mAnimation;
}
@Override
public void apply(Transaction t, SurfaceControl leash, long currentPlayTime) {
final TmpValues tmp = mThreadLocalTmps.get();
@@ -106,7 +128,9 @@ public class WindowAnimationSpec implements AnimationSpec {
boolean cropSet = false;
if (mRootTaskClipMode == ROOT_TASK_CLIP_NONE) {
if (tmp.transformation.hasClipRect()) {
t.setWindowCrop(leash, tmp.transformation.getClipRect());
final Rect clipRect = tmp.transformation.getClipRect();
accountForExtension(tmp.transformation, clipRect);
t.setWindowCrop(leash, clipRect);
cropSet = true;
}
} else {
@@ -114,6 +138,7 @@ public class WindowAnimationSpec implements AnimationSpec {
if (tmp.transformation.hasClipRect()) {
mTmpRect.intersect(tmp.transformation.getClipRect());
}
accountForExtension(tmp.transformation, mTmpRect);
t.setWindowCrop(leash, mTmpRect);
cropSet = true;
}
@@ -125,6 +150,14 @@ public class WindowAnimationSpec implements AnimationSpec {
}
}
private void accountForExtension(Transformation transformation, Rect clipRect) {
Insets extensionInsets = Insets.min(transformation.getInsets(), Insets.NONE);
if (!extensionInsets.equals(Insets.NONE)) {
// Extend the surface to allow for the edge extension to be visible
clipRect.inset(extensionInsets);
}
}
@Override
public long calculateStatusBarTransitionStartTime() {
TranslateAnimation openTranslateAnimation = findTranslateAnimation(mAnimation);