Merge "Add edge extension for ActivityEmbedding Shell transition animation" into tm-qpr-dev am: 9baf3ce919

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20118242

Change-Id: I67467166bfa96d31e50a10ec949eea969ae1163a
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
TreeHugger Robot
2022-10-14 12:06:59 +00:00
committed by Automerger Merge Worker
8 changed files with 238 additions and 166 deletions

View File

@@ -130,6 +130,10 @@ class ActivityEmbeddingAnimationAdapter {
if (!cropRect.intersect(mWholeAnimationBounds)) { if (!cropRect.intersect(mWholeAnimationBounds)) {
// Hide the surface when it is outside of the animation area. // Hide the surface when it is outside of the animation area.
t.setAlpha(mLeash, 0); t.setAlpha(mLeash, 0);
} else if (mAnimation.hasExtension()) {
// Allow the surface to be shown in its original bounds in case we want to use edge
// extensions.
cropRect.union(mChange.getEndAbsBounds());
} }
// cropRect is in absolute coordinate, so we need to translate it to surface top left. // cropRect is in absolute coordinate, so we need to translate it to surface top left.

View File

@@ -21,6 +21,7 @@ import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW; import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
import static com.android.wm.shell.transition.TransitionAnimationHelper.addBackgroundToTransition; import static com.android.wm.shell.transition.TransitionAnimationHelper.addBackgroundToTransition;
import static com.android.wm.shell.transition.TransitionAnimationHelper.edgeExtendWindow;
import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet; import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet;
import android.animation.Animator; import android.animation.Animator;
@@ -45,6 +46,7 @@ import com.android.wm.shell.transition.Transitions;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.function.Consumer;
/** To run the ActivityEmbedding animations. */ /** To run the ActivityEmbedding animations. */
class ActivityEmbeddingAnimationRunner { class ActivityEmbeddingAnimationRunner {
@@ -65,10 +67,31 @@ class ActivityEmbeddingAnimationRunner {
void startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, void startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction) { @NonNull SurfaceControl.Transaction finishTransaction) {
// There may be some surface change that we want to apply after the start transaction is
// applied to make sure the surface is ready.
final List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks =
new ArrayList<>();
final Animator animator = createAnimator(info, startTransaction, finishTransaction, final Animator animator = createAnimator(info, startTransaction, finishTransaction,
() -> mController.onAnimationFinished(transition)); () -> mController.onAnimationFinished(transition), postStartTransactionCallbacks);
startTransaction.apply();
animator.start(); // Start the animation.
if (!postStartTransactionCallbacks.isEmpty()) {
// postStartTransactionCallbacks require that the start transaction is already
// applied to run otherwise they may result in flickers and UI inconsistencies.
startTransaction.apply(true /* sync */);
// Run tasks that require startTransaction to already be applied
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
for (Consumer<SurfaceControl.Transaction> postStartTransactionCallback :
postStartTransactionCallbacks) {
postStartTransactionCallback.accept(t);
}
t.apply();
animator.start();
} else {
startTransaction.apply();
animator.start();
}
} }
/** /**
@@ -85,9 +108,13 @@ class ActivityEmbeddingAnimationRunner {
Animator createAnimator(@NonNull TransitionInfo info, Animator createAnimator(@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction, @NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Runnable animationFinishCallback) { @NonNull Runnable animationFinishCallback,
final List<ActivityEmbeddingAnimationAdapter> adapters = @NonNull List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks) {
createAnimationAdapters(info, startTransaction, finishTransaction); final List<ActivityEmbeddingAnimationAdapter> adapters = createAnimationAdapters(info,
startTransaction);
addEdgeExtensionIfNeeded(startTransaction, finishTransaction, postStartTransactionCallbacks,
adapters);
addBackgroundColorIfNeeded(info, startTransaction, finishTransaction, adapters);
long duration = 0; long duration = 0;
for (ActivityEmbeddingAnimationAdapter adapter : adapters) { for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
duration = Math.max(duration, adapter.getDurationHint()); duration = Math.max(duration, adapter.getDurationHint());
@@ -131,8 +158,7 @@ class ActivityEmbeddingAnimationRunner {
*/ */
@NonNull @NonNull
private List<ActivityEmbeddingAnimationAdapter> createAnimationAdapters( private List<ActivityEmbeddingAnimationAdapter> createAnimationAdapters(
@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) {
@NonNull SurfaceControl.Transaction finishTransaction) {
boolean isChangeTransition = false; boolean isChangeTransition = false;
for (TransitionInfo.Change change : info.getChanges()) { for (TransitionInfo.Change change : info.getChanges()) {
if (change.hasFlags(FLAG_IS_BEHIND_STARTING_WINDOW)) { if (change.hasFlags(FLAG_IS_BEHIND_STARTING_WINDOW)) {
@@ -148,25 +174,23 @@ class ActivityEmbeddingAnimationRunner {
return createChangeAnimationAdapters(info, startTransaction); return createChangeAnimationAdapters(info, startTransaction);
} }
if (Transitions.isClosingType(info.getType())) { if (Transitions.isClosingType(info.getType())) {
return createCloseAnimationAdapters(info, startTransaction, finishTransaction); return createCloseAnimationAdapters(info);
} }
return createOpenAnimationAdapters(info, startTransaction, finishTransaction); return createOpenAnimationAdapters(info);
} }
@NonNull @NonNull
private List<ActivityEmbeddingAnimationAdapter> createOpenAnimationAdapters( private List<ActivityEmbeddingAnimationAdapter> createOpenAnimationAdapters(
@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull TransitionInfo info) {
@NonNull SurfaceControl.Transaction finishTransaction) { return createOpenCloseAnimationAdapters(info, true /* isOpening */,
return createOpenCloseAnimationAdapters(info, startTransaction, finishTransaction, mAnimationSpec::loadOpenAnimation);
true /* isOpening */, mAnimationSpec::loadOpenAnimation);
} }
@NonNull @NonNull
private List<ActivityEmbeddingAnimationAdapter> createCloseAnimationAdapters( private List<ActivityEmbeddingAnimationAdapter> createCloseAnimationAdapters(
@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull TransitionInfo info) {
@NonNull SurfaceControl.Transaction finishTransaction) { return createOpenCloseAnimationAdapters(info, false /* isOpening */,
return createOpenCloseAnimationAdapters(info, startTransaction, finishTransaction, mAnimationSpec::loadCloseAnimation);
false /* isOpening */, mAnimationSpec::loadCloseAnimation);
} }
/** /**
@@ -175,8 +199,7 @@ class ActivityEmbeddingAnimationRunner {
*/ */
@NonNull @NonNull
private List<ActivityEmbeddingAnimationAdapter> createOpenCloseAnimationAdapters( private List<ActivityEmbeddingAnimationAdapter> createOpenCloseAnimationAdapters(
@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull TransitionInfo info, boolean isOpening,
@NonNull SurfaceControl.Transaction finishTransaction, boolean isOpening,
@NonNull AnimationProvider animationProvider) { @NonNull AnimationProvider animationProvider) {
// We need to know if the change window is only a partial of the whole animation screen. // We need to know if the change window is only a partial of the whole animation screen.
// If so, we will need to adjust it to make the whole animation screen looks like one. // If so, we will need to adjust it to make the whole animation screen looks like one.
@@ -200,8 +223,7 @@ class ActivityEmbeddingAnimationRunner {
final List<ActivityEmbeddingAnimationAdapter> adapters = new ArrayList<>(); final List<ActivityEmbeddingAnimationAdapter> adapters = new ArrayList<>();
for (TransitionInfo.Change change : openingChanges) { for (TransitionInfo.Change change : openingChanges) {
final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter( final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter(
info, change, startTransaction, finishTransaction, animationProvider, info, change, animationProvider, openingWholeScreenBounds);
openingWholeScreenBounds);
if (isOpening) { if (isOpening) {
adapter.overrideLayer(offsetLayer++); adapter.overrideLayer(offsetLayer++);
} }
@@ -209,8 +231,7 @@ class ActivityEmbeddingAnimationRunner {
} }
for (TransitionInfo.Change change : closingChanges) { for (TransitionInfo.Change change : closingChanges) {
final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter( final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter(
info, change, startTransaction, finishTransaction, animationProvider, info, change, animationProvider, closingWholeScreenBounds);
closingWholeScreenBounds);
if (!isOpening) { if (!isOpening) {
adapter.overrideLayer(offsetLayer++); adapter.overrideLayer(offsetLayer++);
} }
@@ -219,20 +240,51 @@ class ActivityEmbeddingAnimationRunner {
return adapters; return adapters;
} }
/** Adds edge extension to the surfaces that have such an animation property. */
private void addEdgeExtensionIfNeeded(@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks,
@NonNull List<ActivityEmbeddingAnimationAdapter> adapters) {
for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
final Animation animation = adapter.mAnimation;
if (!animation.hasExtension()) {
continue;
}
final TransitionInfo.Change change = adapter.mChange;
if (Transitions.isOpeningType(adapter.mChange.getMode())) {
// Need to screenshot after startTransaction is applied otherwise activity
// may not be visible or ready yet.
postStartTransactionCallbacks.add(
t -> edgeExtendWindow(change, animation, t, finishTransaction));
} else {
// Can screenshot now (before startTransaction is applied)
edgeExtendWindow(change, animation, startTransaction, finishTransaction);
}
}
}
/** Adds background color to the transition if any animation has such a property. */
private void addBackgroundColorIfNeeded(@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull List<ActivityEmbeddingAnimationAdapter> adapters) {
for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
final int backgroundColor = getTransitionBackgroundColorIfSet(info, adapter.mChange,
adapter.mAnimation, 0 /* defaultColor */);
if (backgroundColor != 0) {
// We only need to show one color.
addBackgroundToTransition(info.getRootLeash(), backgroundColor, startTransaction,
finishTransaction);
return;
}
}
}
@NonNull @NonNull
private ActivityEmbeddingAnimationAdapter createOpenCloseAnimationAdapter( private ActivityEmbeddingAnimationAdapter createOpenCloseAnimationAdapter(
@NonNull TransitionInfo info, @NonNull TransitionInfo.Change change, @NonNull TransitionInfo info, @NonNull TransitionInfo.Change change,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull AnimationProvider animationProvider, @NonNull Rect wholeAnimationBounds) { @NonNull AnimationProvider animationProvider, @NonNull Rect wholeAnimationBounds) {
final Animation animation = animationProvider.get(info, change, wholeAnimationBounds); final Animation animation = animationProvider.get(info, change, wholeAnimationBounds);
// We may want to show a background color for open/close transition.
final int backgroundColor = getTransitionBackgroundColorIfSet(info, change, animation,
0 /* defaultColor */);
if (backgroundColor != 0) {
addBackgroundToTransition(info.getRootLeash(), backgroundColor, startTransaction,
finishTransaction);
}
return new ActivityEmbeddingAnimationAdapter(animation, change, change.getLeash(), return new ActivityEmbeddingAnimationAdapter(animation, change, change.getLeash(),
wholeAnimationBounds); wholeAnimationBounds);
} }

View File

@@ -181,15 +181,15 @@ class ActivityEmbeddingAnimationSpec {
@NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) { @NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) {
final boolean isEnter = Transitions.isOpeningType(change.getMode()); final boolean isEnter = Transitions.isOpeningType(change.getMode());
final Animation animation; final Animation animation;
// TODO(b/207070762): Implement edgeExtension version
if (shouldShowBackdrop(info, change)) { if (shouldShowBackdrop(info, change)) {
animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
? com.android.internal.R.anim.task_fragment_clear_top_open_enter ? com.android.internal.R.anim.task_fragment_clear_top_open_enter
: com.android.internal.R.anim.task_fragment_clear_top_open_exit); : com.android.internal.R.anim.task_fragment_clear_top_open_exit);
} else { } else {
// Use the same edge extension animation as regular activity open.
animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
? com.android.internal.R.anim.task_fragment_open_enter ? com.android.internal.R.anim.activity_open_enter
: com.android.internal.R.anim.task_fragment_open_exit); : com.android.internal.R.anim.activity_open_exit);
} }
// Use the whole animation bounds instead of the change bounds, so that when multiple change // Use the whole animation bounds instead of the change bounds, so that when multiple change
// targets are opening at the same time, the animation applied to each will be the same. // targets are opening at the same time, the animation applied to each will be the same.
@@ -205,15 +205,15 @@ class ActivityEmbeddingAnimationSpec {
@NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) { @NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) {
final boolean isEnter = Transitions.isOpeningType(change.getMode()); final boolean isEnter = Transitions.isOpeningType(change.getMode());
final Animation animation; final Animation animation;
// TODO(b/207070762): Implement edgeExtension version
if (shouldShowBackdrop(info, change)) { if (shouldShowBackdrop(info, change)) {
animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
? com.android.internal.R.anim.task_fragment_clear_top_close_enter ? com.android.internal.R.anim.task_fragment_clear_top_close_enter
: com.android.internal.R.anim.task_fragment_clear_top_close_exit); : com.android.internal.R.anim.task_fragment_clear_top_close_exit);
} else { } else {
// Use the same edge extension animation as regular activity close.
animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
? com.android.internal.R.anim.task_fragment_close_enter ? com.android.internal.R.anim.activity_close_enter
: com.android.internal.R.anim.task_fragment_close_exit); : com.android.internal.R.anim.activity_close_exit);
} }
// Use the whole animation bounds instead of the change bounds, so that when multiple change // Use the whole animation bounds instead of the change bounds, so that when multiple change
// targets are closing at the same time, the animation applied to each will be the same. // targets are closing at the same time, the animation applied to each will be the same.

View File

@@ -59,6 +59,7 @@ import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITI
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_NONE; import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_NONE;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_OPEN; import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_OPEN;
import static com.android.wm.shell.transition.TransitionAnimationHelper.addBackgroundToTransition; import static com.android.wm.shell.transition.TransitionAnimationHelper.addBackgroundToTransition;
import static com.android.wm.shell.transition.TransitionAnimationHelper.edgeExtendWindow;
import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet; import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet;
import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation; import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation;
import static com.android.wm.shell.transition.TransitionAnimationHelper.sDisableCustomTaskAnimationProperty; import static com.android.wm.shell.transition.TransitionAnimationHelper.sDisableCustomTaskAnimationProperty;
@@ -76,10 +77,7 @@ import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.graphics.Canvas;
import android.graphics.Insets; import android.graphics.Insets;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Point; import android.graphics.Point;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
@@ -89,7 +87,6 @@ import android.os.IBinder;
import android.os.UserHandle; import android.os.UserHandle;
import android.util.ArrayMap; import android.util.ArrayMap;
import android.view.Choreographer; import android.view.Choreographer;
import android.view.Surface;
import android.view.SurfaceControl; import android.view.SurfaceControl;
import android.view.SurfaceSession; import android.view.SurfaceSession;
import android.view.WindowManager; import android.view.WindowManager;
@@ -525,123 +522,6 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
} }
} }
private void edgeExtendWindow(TransitionInfo.Change change,
Animation a, SurfaceControl.Transaction startTransaction,
SurfaceControl.Transaction finishTransaction) {
// Do not create edge extension surface for transfer starting window change.
// The app surface could be empty thus nothing can draw on the hardware renderer, which will
// block this thread when calling Surface#unlockCanvasAndPost.
if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
return;
}
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;
}
@Nullable @Nullable
@Override @Override
public WindowContainerTransaction handleRequest(@NonNull IBinder transition, public WindowContainerTransaction handleRequest(@NonNull IBinder transition,

View File

@@ -24,6 +24,7 @@ import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.transitTypeToString; import static android.view.WindowManager.transitTypeToString;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT; import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CLOSE; import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CLOSE;
@@ -34,10 +35,19 @@ import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITI
import android.annotation.ColorInt; import android.annotation.ColorInt;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.Shader;
import android.os.SystemProperties; import android.os.SystemProperties;
import android.view.Surface;
import android.view.SurfaceControl; import android.view.SurfaceControl;
import android.view.animation.Animation; import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.window.TransitionInfo; import android.window.TransitionInfo;
import com.android.internal.R; import com.android.internal.R;
@@ -217,4 +227,126 @@ public class TransitionAnimationHelper {
.show(animationBackgroundSurface); .show(animationBackgroundSurface);
finishTransaction.remove(animationBackgroundSurface); finishTransaction.remove(animationBackgroundSurface);
} }
/**
* Adds edge extension surface to the given {@code change} for edge extension animation.
*/
public static void edgeExtendWindow(@NonNull TransitionInfo.Change change,
@NonNull Animation a, @NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction) {
// Do not create edge extension surface for transfer starting window change.
// The app surface could be empty thus nothing can draw on the hardware renderer, which will
// block this thread when calling Surface#unlockCanvasAndPost.
if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
return;
}
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);
}
}
/**
* Takes a screenshot of {@code surfaceToExtend}'s edge and extends it for edge extension
* animation.
*/
private static SurfaceControl createExtensionSurface(@NonNull SurfaceControl surfaceToExtend,
@NonNull Rect edgeBounds, @NonNull Rect extensionRect, int xPos, int yPos,
@NonNull String layerName, @NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction) {
final SurfaceControl edgeExtensionLayer = new SurfaceControl.Builder()
.setName(layerName)
.setParent(surfaceToExtend)
.setHidden(true)
.setCallsite("TransitionAnimationHelper#createExtensionSurface")
.setOpaque(true)
.setBufferSize(extensionRect.width(), extensionRect.height())
.build();
final 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;
}
final BitmapShader shader = new BitmapShader(edgeBuffer.asBitmap(),
Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
final Paint paint = new Paint();
paint.setShader(shader);
final Surface surface = new Surface(edgeExtensionLayer);
final 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;
}
} }

View File

@@ -40,6 +40,8 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import java.util.ArrayList;
/** /**
* Tests for {@link ActivityEmbeddingAnimationRunner}. * Tests for {@link ActivityEmbeddingAnimationRunner}.
* *
@@ -62,13 +64,13 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim
final TransitionInfo.Change embeddingChange = createChange(); final TransitionInfo.Change embeddingChange = createChange();
embeddingChange.setFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY); embeddingChange.setFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY);
info.addChange(embeddingChange); info.addChange(embeddingChange);
doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any()); doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any(), any());
mAnimRunner.startAnimation(mTransition, info, mStartTransaction, mFinishTransaction); mAnimRunner.startAnimation(mTransition, info, mStartTransaction, mFinishTransaction);
final ArgumentCaptor<Runnable> finishCallback = ArgumentCaptor.forClass(Runnable.class); final ArgumentCaptor<Runnable> finishCallback = ArgumentCaptor.forClass(Runnable.class);
verify(mAnimRunner).createAnimator(eq(info), eq(mStartTransaction), eq(mFinishTransaction), verify(mAnimRunner).createAnimator(eq(info), eq(mStartTransaction), eq(mFinishTransaction),
finishCallback.capture()); finishCallback.capture(), any());
verify(mStartTransaction).apply(); verify(mStartTransaction).apply();
verify(mAnimator).start(); verify(mAnimator).start();
verifyNoMoreInteractions(mFinishTransaction); verifyNoMoreInteractions(mFinishTransaction);
@@ -88,7 +90,8 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim
info.addChange(embeddingChange); info.addChange(embeddingChange);
final Animator animator = mAnimRunner.createAnimator( final Animator animator = mAnimRunner.createAnimator(
info, mStartTransaction, mFinishTransaction, info, mStartTransaction, mFinishTransaction,
() -> mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */)); () -> mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */),
new ArrayList());
// The animation should be empty when it is behind starting window. // The animation should be empty when it is behind starting window.
assertEquals(0, animator.getDuration()); assertEquals(0, animator.getDuration());

View File

@@ -56,13 +56,12 @@ abstract class ActivityEmbeddingAnimationTestBase extends ShellTestCase {
@Mock @Mock
SurfaceControl.Transaction mFinishTransaction; SurfaceControl.Transaction mFinishTransaction;
@Mock @Mock
Transitions.TransitionFinishCallback mFinishCallback;
@Mock
Animator mAnimator; Animator mAnimator;
ActivityEmbeddingController mController; ActivityEmbeddingController mController;
ActivityEmbeddingAnimationRunner mAnimRunner; ActivityEmbeddingAnimationRunner mAnimRunner;
ActivityEmbeddingAnimationSpec mAnimSpec; ActivityEmbeddingAnimationSpec mAnimSpec;
Transitions.TransitionFinishCallback mFinishCallback;
@CallSuper @CallSuper
@Before @Before
@@ -75,9 +74,11 @@ abstract class ActivityEmbeddingAnimationTestBase extends ShellTestCase {
assertNotNull(mAnimRunner); assertNotNull(mAnimRunner);
mAnimSpec = mAnimRunner.mAnimationSpec; mAnimSpec = mAnimRunner.mAnimationSpec;
assertNotNull(mAnimSpec); assertNotNull(mAnimSpec);
mFinishCallback = (wct, wctCB) -> {};
spyOn(mController); spyOn(mController);
spyOn(mAnimRunner); spyOn(mAnimRunner);
spyOn(mAnimSpec); spyOn(mAnimSpec);
spyOn(mFinishCallback);
} }
/** Creates a mock {@link TransitionInfo.Change}. */ /** Creates a mock {@link TransitionInfo.Change}. */

View File

@@ -55,7 +55,7 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation
@Before @Before
public void setup() { public void setup() {
super.setUp(); super.setUp();
doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any()); doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any(), any());
} }
@Test @Test