From c72e0d110f02367cf37b399a007f9be61aefbfc2 Mon Sep 17 00:00:00 2001 From: Chris Li Date: Mon, 5 Sep 2022 16:20:32 +0800 Subject: [PATCH] Fix ActivityEmbedding Shell transition animation with reparent With legacy transition, we capture a screenshot of the reparent child, and attach it to the parent surface (in WM Core), and then animate the parent in handler. With Shell transition, we also need to do the same (in WM Shell) to screenshot the reparent child, and animate the parent using the child's change info. Otherwise the child animation will be cropped. Bug: 207070762 Test: manual Change-Id: I52b5c839343864b4179e5c35c33343bc282bc189 --- .../ActivityEmbeddingAnimationAdapter.java | 4 +- .../ActivityEmbeddingAnimationRunner.java | 111 ++++++++++++------ .../wm/shell/common/ScreenshotUtils.java | 29 ++++- .../com/android/server/wm/ActivityRecord.java | 13 +- 4 files changed, 115 insertions(+), 42 deletions(-) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java index cc4db933ec9fc..f5acc5dd540ae 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java @@ -196,7 +196,9 @@ class ActivityEmbeddingAnimationAdapter { void onAnimationEnd(@NonNull SurfaceControl.Transaction t) { super.onAnimationEnd(t); // Remove the screenshot leash after animation is finished. - t.remove(mLeash); + if (mLeash.isValid()) { + t.remove(mLeash); + } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java index 7e0795d111537..74f89bdcddb49 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java @@ -25,6 +25,7 @@ import android.content.Context; import android.graphics.Point; import android.graphics.Rect; import android.os.IBinder; +import android.util.ArraySet; import android.util.Log; import android.view.SurfaceControl; import android.view.animation.Animation; @@ -40,6 +41,7 @@ import com.android.wm.shell.transition.Transitions; import java.util.ArrayList; import java.util.List; +import java.util.Set; import java.util.function.BiFunction; /** To run the ActivityEmbedding animations. */ @@ -232,38 +234,65 @@ class ActivityEmbeddingAnimationRunner { private List createChangeAnimationAdapters( @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) { final List adapters = new ArrayList<>(); + final Set handledChanges = new ArraySet<>(); + + // For the first iteration, we prepare the animation for the change type windows. This is + // needed because there may be window that is reparented while resizing. In such case, we + // will do the following: + // 1. Capture a screenshot from the Activity surface. + // 2. Attach the screenshot surface to the top of TaskFragment (Activity's parent) surface. + // 3. Animate the TaskFragment using Activity Change info (start/end bounds). + // This is because the TaskFragment surface/change won't contain the Activity's before its + // reparent. for (TransitionInfo.Change change : info.getChanges()) { - if (change.getMode() == TRANSIT_CHANGE - && !change.getStartAbsBounds().equals(change.getEndAbsBounds())) { - // This is the window with bounds change. - final WindowContainerToken parentToken = change.getParent(); - final Rect parentBounds; - if (parentToken != null) { - TransitionInfo.Change parentChange = info.getChange(parentToken); - parentBounds = parentChange != null - ? parentChange.getEndAbsBounds() - : change.getEndAbsBounds(); - } else { - parentBounds = change.getEndAbsBounds(); - } - final Animation[] animations = - mAnimationSpec.createChangeBoundsChangeAnimations(change, parentBounds); - // Adapter for the starting screenshot leash. - final SurfaceControl screenshotLeash = createScreenshot(change, startTransaction); - if (screenshotLeash != null) { - // The screenshot leash will be removed in SnapshotAdapter#onAnimationEnd - adapters.add(new ActivityEmbeddingAnimationAdapter.SnapshotAdapter( - animations[0], change, screenshotLeash)); - } else { - Log.e(TAG, "Failed to take screenshot for change=" + change); - } - // Adapter for the ending bounds changed leash. - adapters.add(new ActivityEmbeddingAnimationAdapter.BoundsChangeAdapter( - animations[1], change)); + if (change.getMode() != TRANSIT_CHANGE + || change.getStartAbsBounds().equals(change.getEndAbsBounds())) { + continue; + } + + // This is the window with bounds change. + handledChanges.add(change); + final WindowContainerToken parentToken = change.getParent(); + TransitionInfo.Change boundsAnimationChange = change; + if (parentToken != null) { + // When the parent window is also included in the transition as an opening window, + // we would like to animate the parent window instead. + final TransitionInfo.Change parentChange = info.getChange(parentToken); + if (parentChange != null && Transitions.isOpeningType(parentChange.getMode())) { + // We won't create a separate animation for the parent, but to animate the + // parent for the child resizing. + handledChanges.add(parentChange); + boundsAnimationChange = parentChange; + } + } + + final Animation[] animations = mAnimationSpec.createChangeBoundsChangeAnimations(change, + boundsAnimationChange.getEndAbsBounds()); + + // Create a screenshot based on change, but attach it to the top of the + // boundsAnimationChange. + final SurfaceControl screenshotLeash = getOrCreateScreenshot(change, + boundsAnimationChange, startTransaction); + if (screenshotLeash != null) { + // Adapter for the starting screenshot leash. + // The screenshot leash will be removed in SnapshotAdapter#onAnimationEnd + adapters.add(new ActivityEmbeddingAnimationAdapter.SnapshotAdapter( + animations[0], change, screenshotLeash)); + } else { + Log.e(TAG, "Failed to take screenshot for change=" + change); + } + // Adapter for the ending bounds changed leash. + adapters.add(new ActivityEmbeddingAnimationAdapter.BoundsChangeAdapter( + animations[1], boundsAnimationChange)); + } + + // Handle the other windows that don't have bounds change in the same transition. + for (TransitionInfo.Change change : info.getChanges()) { + if (handledChanges.contains(change)) { + // Skip windows that we have already handled in the previous iteration. continue; } - // These are the other windows that don't have bounds change in the same transition. final Animation animation; if (!TransitionInfo.isIndependent(change, info)) { // No-op if it will be covered by the changing parent window. @@ -278,13 +307,27 @@ class ActivityEmbeddingAnimationRunner { return adapters; } - /** Takes a screenshot of the given {@link TransitionInfo.Change} surface. */ + /** + * Takes a screenshot of the given {@code screenshotChange} surface if WM Core hasn't taken one. + * The screenshot leash should be attached to the {@code animationChange} surface which we will + * animate later. + */ @Nullable - private SurfaceControl createScreenshot(@NonNull TransitionInfo.Change change, - @NonNull SurfaceControl.Transaction startTransaction) { - final Rect cropBounds = new Rect(change.getStartAbsBounds()); + private SurfaceControl getOrCreateScreenshot(@NonNull TransitionInfo.Change screenshotChange, + @NonNull TransitionInfo.Change animationChange, + @NonNull SurfaceControl.Transaction t) { + final SurfaceControl screenshotLeash = screenshotChange.getSnapshot(); + if (screenshotLeash != null) { + // If WM Core has already taken a screenshot, make sure it is reparented to the + // animation leash. + t.reparent(screenshotLeash, animationChange.getLeash()); + return screenshotLeash; + } + + // If WM Core hasn't taken a screenshot, take a screenshot now. + final Rect cropBounds = new Rect(screenshotChange.getStartAbsBounds()); cropBounds.offsetTo(0, 0); - return ScreenshotUtils.takeScreenshot(startTransaction, change.getLeash(), cropBounds, - Integer.MAX_VALUE); + return ScreenshotUtils.takeScreenshot(t, screenshotChange.getLeash(), + animationChange.getLeash(), cropBounds, Integer.MAX_VALUE); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java index c4bd73ba1b4a5..2a1bf0ee42baa 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java @@ -28,7 +28,7 @@ import java.util.function.Consumer; public class ScreenshotUtils { /** - * Take a screenshot of the specified SurfaceControl. + * Takes a screenshot of the specified SurfaceControl. * * @param sc the SurfaceControl to take a screenshot of * @param crop the crop to use when capturing the screenshot @@ -49,11 +49,14 @@ public class ScreenshotUtils { SurfaceControl mScreenshot = null; SurfaceControl.Transaction mTransaction; SurfaceControl mSurfaceControl; + SurfaceControl mParentSurfaceControl; int mLayer; - BufferConsumer(SurfaceControl.Transaction t, SurfaceControl sc, int layer) { + BufferConsumer(SurfaceControl.Transaction t, SurfaceControl sc, SurfaceControl parentSc, + int layer) { mTransaction = t; mSurfaceControl = sc; + mParentSurfaceControl = parentSc; mLayer = layer; } @@ -72,7 +75,7 @@ public class ScreenshotUtils { mTransaction.setBuffer(mScreenshot, buffer.getHardwareBuffer()); mTransaction.setColorSpace(mScreenshot, buffer.getColorSpace()); - mTransaction.reparent(mScreenshot, mSurfaceControl); + mTransaction.reparent(mScreenshot, mParentSurfaceControl); mTransaction.setLayer(mScreenshot, mLayer); mTransaction.show(mScreenshot); mTransaction.apply(); @@ -80,7 +83,7 @@ public class ScreenshotUtils { } /** - * Take a screenshot of the specified SurfaceControl. + * Takes a screenshot of the specified SurfaceControl. * * @param t the transaction used to set changes on the resulting screenshot. * @param sc the SurfaceControl to take a screenshot of @@ -91,7 +94,23 @@ public class ScreenshotUtils { */ public static SurfaceControl takeScreenshot(SurfaceControl.Transaction t, SurfaceControl sc, Rect crop, int layer) { - BufferConsumer consumer = new BufferConsumer(t, sc, layer); + return takeScreenshot(t, sc, sc /* parentSc */, crop, layer); + } + + /** + * Takes a screenshot of the specified SurfaceControl. + * + * @param t the transaction used to set changes on the resulting screenshot. + * @param sc the SurfaceControl to take a screenshot of + * @param parentSc the SurfaceControl to attach the screenshot to. + * @param crop the crop to use when capturing the screenshot + * @param layer the layer to place the screenshot + * + * @return A SurfaceControl where the screenshot will be attached, or null if failed. + */ + public static SurfaceControl takeScreenshot(SurfaceControl.Transaction t, SurfaceControl sc, + SurfaceControl parentSc, Rect crop, int layer) { + BufferConsumer consumer = new BufferConsumer(t, sc, parentSc, layer); captureLayer(sc, crop, consumer); return consumer.mScreenshot; } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 6538ee3c2182e..4c0a0171024a9 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1522,8 +1522,17 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A this.task = newTask; if (shouldStartChangeTransition(newParent, oldParent)) { - // Animate change transition on TaskFragment level to get the correct window crop. - newParent.initializeChangeTransition(getBounds(), getSurfaceControl()); + if (mTransitionController.isShellTransitionsEnabled()) { + // For Shell transition, call #initializeChangeTransition directly to take the + // screenshot at the Activity level. And Shell will be in charge of handling the + // surface reparent and crop. + initializeChangeTransition(getBounds()); + } else { + // For legacy app transition, we want to take a screenshot of the Activity surface, + // but animate the change transition on TaskFragment level to get the correct window + // crop. + newParent.initializeChangeTransition(getBounds(), getSurfaceControl()); + } } super.onParentChanged(newParent, oldParent);