From 17558b56807b7e3dbbfb1a42db04456e7a1219ba Mon Sep 17 00:00:00 2001 From: Chris Li Date: Sat, 25 Sep 2021 10:04:25 +0800 Subject: [PATCH] Offset animation leash to the relative position When the snapshot surface is created, it should remain at its original position. When the animation starts, change animation bounds should be at the start bounds relative position. Bug: 196173550 Test: atest WmTests:RemoteAnimationControllerTest Test: Test with demo app full -> split with RTL enabled. Change-Id: I3cb44e1f222870f51105bfaa98fc4febaafec03b --- .../embedding/TaskFragmentAnimationSpec.java | 4 +- .../com/android/server/wm/ActivityRecord.java | 6 +- .../server/wm/RemoteAnimationController.java | 43 +++++++------ .../com/android/server/wm/SurfaceFreezer.java | 6 +- .../android/server/wm/WindowContainer.java | 13 +++- .../wm/RemoteAnimationControllerTest.java | 61 ++++++++++++++++++- 6 files changed, 105 insertions(+), 28 deletions(-) diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java index 1e64782b161ff..11a79b2f4a27d 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java @@ -121,9 +121,11 @@ class TaskFragmentAnimationSpec { * the second one is for the end leash. */ Animation[] createChangeBoundsChangeAnimations(@NonNull RemoteAnimationTarget target) { + // Both start bounds and end bounds are in screen coordinates. We will post translate + // to the local coordinates in TaskFragmentAnimationAdapter#onAnimationUpdate final Rect startBounds = target.startBounds; final Rect parentBounds = target.taskInfo.configuration.windowConfiguration.getBounds(); - final Rect endBounds = target.localBounds; + final Rect endBounds = target.screenSpaceBounds; float scaleX = ((float) startBounds.width()) / endBounds.width(); float scaleY = ((float) startBounds.height()) / endBounds.height(); // Start leash is a child of the end leash. Reverse the scale so that the start leash won't diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 19a9686282485..195da18a88be8 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1411,7 +1411,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A this.task = newTask; if (shouldStartChangeTransition(newParent, oldParent)) { - initializeChangeTransition(getBounds()); + // The new parent and old parent may be in different position. Need to offset the + // animation surface to keep it in its original position. + initializeChangeTransition(getBounds(), newParent.getBounds()); } super.onParentChanged(newParent, oldParent); @@ -9276,7 +9278,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A record.getMode(), record.mAdapter.mCapturedLeash, !fillsParent(), new Rect(), insets, getPrefixOrderIndex(), record.mAdapter.mPosition, record.mAdapter.mLocalBounds, - record.mAdapter.mRootTaskBounds, task.getWindowConfiguration(), + record.mAdapter.mEndBounds, task.getWindowConfiguration(), false /*isNotInRecents*/, record.mThumbnailAdapter != null ? record.mThumbnailAdapter.mCapturedLeash : null, record.mStartBounds, task.getTaskInfo(), checkEnterPictureInPictureAppOpsState()); diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java index b90f937ca56ec..16a45fe7cec7e 100644 --- a/services/core/java/com/android/server/wm/RemoteAnimationController.java +++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java @@ -65,7 +65,6 @@ class RemoteAnimationController implements DeathRecipient { new ArrayList<>(); @VisibleForTesting final ArrayList mPendingNonAppAnimations = new ArrayList<>(); - private final Rect mTmpRect = new Rect(); private final Handler mHandler; private final Runnable mTimeoutRunnable = () -> cancelAnimation("timeoutRunnable"); @@ -85,18 +84,18 @@ class RemoteAnimationController implements DeathRecipient { * Creates an animation record for each individual {@link WindowContainer}. * * @param windowContainer The windows to animate. - * @param position The position app bounds, in screen coordinates. + * @param position The position app bounds relative to its parent. * @param localBounds The bounds of the app relative to its parent. - * @param stackBounds The stack bounds of the app relative to position. - * @param startBounds The stack bounds before the transition, in screen coordinates + * @param endBounds The end bounds after the transition, in screen coordinates. + * @param startBounds The start bounds before the transition, in screen coordinates. * @return The record representing animation(s) to run on the app. */ RemoteAnimationRecord createRemoteAnimationRecord(WindowContainer windowContainer, - Point position, Rect localBounds, Rect stackBounds, Rect startBounds) { + Point position, Rect localBounds, Rect endBounds, Rect startBounds) { ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "createAnimationAdapter(): container=%s", windowContainer); final RemoteAnimationRecord adapters = new RemoteAnimationRecord(windowContainer, position, - localBounds, stackBounds, startBounds); + localBounds, endBounds, startBounds); mPendingAnimations.add(adapters); return adapters; } @@ -405,16 +404,17 @@ class RemoteAnimationController implements DeathRecipient { mStartBounds = new Rect(startBounds); mAdapter = new RemoteAnimationAdapterWrapper(this, endPos, localBounds, endBounds, mStartBounds); - mTmpRect.set(startBounds); - mTmpRect.offsetTo(0, 0); if (mRemoteAnimationAdapter.getChangeNeedsSnapshot()) { - mThumbnailAdapter = - new RemoteAnimationAdapterWrapper(this, new Point(0, 0), localBounds, - mTmpRect, new Rect()); + final Rect thumbnailLocalBounds = new Rect(startBounds); + thumbnailLocalBounds.offsetTo(0, 0); + // Snapshot is located at (0,0) of the animation leash. It doesn't have size + // change, so the startBounds is its end bounds, and no start bounds for it. + mThumbnailAdapter = new RemoteAnimationAdapterWrapper(this, new Point(0, 0), + thumbnailLocalBounds, startBounds, new Rect()); } } else { mAdapter = new RemoteAnimationAdapterWrapper(this, endPos, localBounds, endBounds, - new Rect(endPos.x, endPos.y, endBounds.right, endBounds.bottom)); + new Rect()); mStartBounds = null; } } @@ -458,15 +458,15 @@ class RemoteAnimationController implements DeathRecipient { private @AnimationType int mAnimationType; final Point mPosition = new Point(); final Rect mLocalBounds; - final Rect mRootTaskBounds = new Rect(); + final Rect mEndBounds = new Rect(); final Rect mStartBounds = new Rect(); RemoteAnimationAdapterWrapper(RemoteAnimationRecord record, Point position, - Rect localBounds, Rect rootTaskBounds, Rect startBounds) { + Rect localBounds, Rect endBounds, Rect startBounds) { mRecord = record; mPosition.set(position.x, position.y); mLocalBounds = localBounds; - mRootTaskBounds.set(rootTaskBounds); + mEndBounds.set(endBounds); mStartBounds.set(startBounds); } @@ -480,12 +480,17 @@ class RemoteAnimationController implements DeathRecipient { @AnimationType int type, OnAnimationFinishedCallback finishCallback) { ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "startAnimation"); - // Restore position and stack crop until client has a chance to modify it. if (mStartBounds.isEmpty()) { - t.setPosition(animationLeash, 0, 0); - t.setWindowCrop(animationLeash, -1, -1); + // Restore position and stack crop until client has a chance to modify it. + t.setPosition(animationLeash, mPosition.x, mPosition.y); + t.setWindowCrop(animationLeash, mEndBounds.width(), mEndBounds.height()); } else { - t.setPosition(animationLeash, mStartBounds.left, mStartBounds.top); + // Offset the change animation leash to the relative start position in parent. + // (mPosition) is the relative end position in parent container. + // (mStartBounds - mEndBounds) is the position difference between start and end. + // (mPosition + mStartBounds - mEndBounds) will be the relative start position. + t.setPosition(animationLeash, mPosition.x + mStartBounds.left - mEndBounds.left, + mPosition.y + mStartBounds.top - mEndBounds.top); t.setWindowCrop(animationLeash, mStartBounds.width(), mStartBounds.height()); } mCapturedLeash = animationLeash; diff --git a/services/core/java/com/android/server/wm/SurfaceFreezer.java b/services/core/java/com/android/server/wm/SurfaceFreezer.java index fce2f8da10c78..00ce1e241ca10 100644 --- a/services/core/java/com/android/server/wm/SurfaceFreezer.java +++ b/services/core/java/com/android/server/wm/SurfaceFreezer.java @@ -24,6 +24,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.GraphicBuffer; import android.graphics.PixelFormat; +import android.graphics.Point; import android.graphics.Rect; import android.hardware.HardwareBuffer; import android.view.Surface; @@ -70,13 +71,14 @@ class SurfaceFreezer { * above the target surface) and then taking a snapshot and placing it over the target surface. * * @param startBounds The original bounds (on screen) of the surface we are snapshotting. + * @param relativePosition The related position of the snapshot surface to its parent. */ - void freeze(SurfaceControl.Transaction t, Rect startBounds) { + void freeze(SurfaceControl.Transaction t, Rect startBounds, Point relativePosition) { mFreezeBounds.set(startBounds); mLeash = SurfaceAnimator.createAnimationLeash(mAnimatable, mAnimatable.getSurfaceControl(), t, ANIMATION_TYPE_SCREEN_ROTATION, startBounds.width(), startBounds.height(), - startBounds.left, startBounds.top, false /* hidden */, + relativePosition.x, relativePosition.y, false /* hidden */, mWmService.mTransactionFactory); mAnimatable.onAnimationLeashCreated(t, mLeash); diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 26fe2fc596042..7fcf9b2614f2f 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -2589,11 +2589,20 @@ class WindowContainer extends ConfigurationContainer< * 3. {@link ActivityRecord} is reparented into an organized {@link TaskFragment}. * * This shouldn't be called on other {@link WindowContainer} unless there is a valid use case. + * + * @param startBounds The original bounds (on screen) of the surface we are snapshotting. + * @param parentBounds The parent bounds (on screen) to calculate the animation surface + * position. */ - void initializeChangeTransition(Rect startBounds) { + void initializeChangeTransition(Rect startBounds, Rect parentBounds) { mDisplayContent.prepareAppTransition(TRANSIT_CHANGE); mDisplayContent.mChangingContainers.add(this); - mSurfaceFreezer.freeze(getSyncTransaction(), startBounds); + mTmpPoint.set(startBounds.left - parentBounds.left, startBounds.top - parentBounds.top); + mSurfaceFreezer.freeze(getSyncTransaction(), startBounds, mTmpPoint); + } + + void initializeChangeTransition(Rect startBounds) { + initializeChangeTransition(startBounds, getParent().getBounds()); } ArraySet getAnimationSources() { diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java index 89ae5ed9b4251..08312ef0b8651 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java @@ -341,7 +341,8 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { verify(mMockTransaction).setWindowCrop( mMockLeash, app.startBounds.width(), app.startBounds.height()); verify(mMockTransaction).setPosition(mMockThumbnailLeash, 0, 0); - verify(mMockTransaction).setWindowCrop(mMockThumbnailLeash, -1, -1); + verify(mMockTransaction).setWindowCrop(mMockThumbnailLeash, app.startBounds.width(), + app.startBounds.height()); finishedCaptor.getValue().onAnimationFinished(); verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_WINDOW_ANIMATION), @@ -394,7 +395,63 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { verify(mMockTransaction).setWindowCrop( mMockLeash, app.startBounds.width(), app.startBounds.height()); verify(mMockTransaction).setPosition(mMockThumbnailLeash, 0, 0); - verify(mMockTransaction).setWindowCrop(mMockThumbnailLeash, -1, -1); + verify(mMockTransaction).setWindowCrop(mMockThumbnailLeash, app.startBounds.width(), + app.startBounds.height()); + + finishedCaptor.getValue().onAnimationFinished(); + verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_WINDOW_ANIMATION), + eq(record.mAdapter)); + verify(mThumbnailFinishedCallback).onAnimationFinished( + eq(ANIMATION_TYPE_WINDOW_ANIMATION), eq(record.mThumbnailAdapter)); + } finally { + mDisplayContent.mChangingContainers.clear(); + } + } + + @Test + public void testChangeToDifferentPosition() throws Exception { + final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); + mDisplayContent.mChangingContainers.add(win.mActivityRecord); + try { + final RemoteAnimationRecord record = mController.createRemoteAnimationRecord( + win.mActivityRecord, new Point(100, 100), null, new Rect(150, 150, 400, 400), + new Rect(50, 100, 150, 150)); + assertNotNull(record.mThumbnailAdapter); + ((AnimationAdapter) record.mAdapter) + .startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_WINDOW_ANIMATION, + mFinishedCallback); + ((AnimationAdapter) record.mThumbnailAdapter).startAnimation(mMockThumbnailLeash, + mMockTransaction, ANIMATION_TYPE_WINDOW_ANIMATION, mThumbnailFinishedCallback); + mController.goodToGo(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE); + mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); + final ArgumentCaptor appsCaptor = + ArgumentCaptor.forClass(RemoteAnimationTarget[].class); + final ArgumentCaptor wallpapersCaptor = + ArgumentCaptor.forClass(RemoteAnimationTarget[].class); + final ArgumentCaptor nonAppsCaptor = + ArgumentCaptor.forClass(RemoteAnimationTarget[].class); + final ArgumentCaptor finishedCaptor = + ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class); + verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE), + appsCaptor.capture(), wallpapersCaptor.capture(), nonAppsCaptor.capture(), + finishedCaptor.capture()); + assertEquals(1, appsCaptor.getValue().length); + final RemoteAnimationTarget app = appsCaptor.getValue()[0]; + assertEquals(RemoteAnimationTarget.MODE_CHANGING, app.mode); + assertEquals(new Point(100, 100), app.position); + assertEquals(new Rect(150, 150, 400, 400), app.sourceContainerBounds); + assertEquals(new Rect(50, 100, 150, 150), app.startBounds); + assertEquals(mMockLeash, app.leash); + assertEquals(mMockThumbnailLeash, app.startLeash); + assertEquals(false, app.isTranslucent); + verify(mMockTransaction).setPosition( + mMockLeash, app.position.x + app.startBounds.left - app.screenSpaceBounds.left, + app.position.y + app.startBounds.top - app.screenSpaceBounds.top); + verify(mMockTransaction).setWindowCrop( + mMockLeash, app.startBounds.width(), app.startBounds.height()); + verify(mMockTransaction).setPosition(mMockThumbnailLeash, 0, 0); + verify(mMockTransaction).setWindowCrop(mMockThumbnailLeash, app.startBounds.width(), + app.startBounds.height()); finishedCaptor.getValue().onAnimationFinished(); verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_WINDOW_ANIMATION),