diff --git a/core/java/android/view/RemoteAnimationTarget.java b/core/java/android/view/RemoteAnimationTarget.java index e407707231cae..8816eac33cfb0 100644 --- a/core/java/android/view/RemoteAnimationTarget.java +++ b/core/java/android/view/RemoteAnimationTarget.java @@ -35,6 +35,7 @@ import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; import android.annotation.ColorInt; import android.annotation.IntDef; +import android.annotation.Nullable; import android.app.ActivityManager; import android.app.TaskInfo; import android.app.WindowConfiguration; @@ -175,10 +176,16 @@ public class RemoteAnimationTarget implements Parcelable { public final Rect screenSpaceBounds; /** - * The starting bounds of the source container in screen space coordinates. This is {@code null} - * if the animation target isn't MODE_CHANGING. Since this is the starting bounds, it's size - * should be equivalent to the size of the starting thumbnail. Note that sourceContainerBounds - * is the end bounds of a change transition. + * The starting bounds of the source container in screen space coordinates. + * For {@link #MODE_OPENING}, this will be equivalent to {@link #screenSpaceBounds}. + * For {@link #MODE_CLOSING}, this will be equivalent to {@link #screenSpaceBounds} unless the + * closing container is also resizing. For example, when ActivityEmbedding split pair becomes + * stacked, the container on the back will be resized to fullscreen, but will also be covered + * (closing) by the container in the front. + * For {@link #MODE_CHANGING}, since this is the starting bounds, its size should be equivalent + * to the bounds of the starting thumbnail. + * + * Note that {@link #screenSpaceBounds} is the end bounds of a transition. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public final Rect startBounds; @@ -247,7 +254,8 @@ public class RemoteAnimationTarget implements Parcelable { Rect clipRect, Rect contentInsets, int prefixOrderIndex, Point position, Rect localBounds, Rect screenSpaceBounds, WindowConfiguration windowConfig, boolean isNotInRecents, - SurfaceControl startLeash, Rect startBounds, ActivityManager.RunningTaskInfo taskInfo, + SurfaceControl startLeash, @Nullable Rect startBounds, + ActivityManager.RunningTaskInfo taskInfo, boolean allowEnterPip) { this(taskId, mode, leash, isTranslucent, clipRect, contentInsets, prefixOrderIndex, position, localBounds, screenSpaceBounds, windowConfig, isNotInRecents, startLeash, @@ -258,7 +266,7 @@ public class RemoteAnimationTarget implements Parcelable { Rect clipRect, Rect contentInsets, int prefixOrderIndex, Point position, Rect localBounds, Rect screenSpaceBounds, WindowConfiguration windowConfig, boolean isNotInRecents, - SurfaceControl startLeash, Rect startBounds, + SurfaceControl startLeash, @Nullable Rect startBounds, ActivityManager.RunningTaskInfo taskInfo, boolean allowEnterPip, @WindowManager.LayoutParams.WindowType int windowType) { this.mode = mode; @@ -275,10 +283,13 @@ public class RemoteAnimationTarget implements Parcelable { this.windowConfiguration = windowConfig; this.isNotInRecents = isNotInRecents; this.startLeash = startLeash; - this.startBounds = startBounds == null ? null : new Rect(startBounds); this.taskInfo = taskInfo; this.allowEnterPip = allowEnterPip; this.windowType = windowType; + // Same as screenSpaceBounds if the window is not resizing. + this.startBounds = startBounds == null + ? new Rect(screenSpaceBounds) + : new Rect(startBounds); } public RemoteAnimationTarget(Parcel in) { @@ -399,9 +410,7 @@ public class RemoteAnimationTarget implements Parcelable { if (startLeash != null) { startLeash.dumpDebug(proto, START_LEASH); } - if (startBounds != null) { - startBounds.dumpDebug(proto, START_BOUNDS); - } + startBounds.dumpDebug(proto, START_BOUNDS); proto.end(token); } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java index af5d8c5618740..33220c44a3b53 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java @@ -18,7 +18,9 @@ package androidx.window.extensions.embedding; import static android.graphics.Matrix.MTRANS_X; import static android.graphics.Matrix.MTRANS_Y; +import static android.view.RemoteAnimationTarget.MODE_CLOSING; +import android.graphics.Point; import android.graphics.Rect; import android.view.Choreographer; import android.view.RemoteAnimationTarget; @@ -49,6 +51,16 @@ class TaskFragmentAnimationAdapter { /** Area in absolute coordinate that the animation surface shouldn't go beyond. */ @NonNull private final Rect mWholeAnimationBounds = new Rect(); + /** + * Area in absolute coordinate that should represent all the content to show for this window. + * This should be the end bounds for opening window, and start bounds for closing window in case + * the window is resizing during the open/close transition. + */ + @NonNull + private final Rect mContentBounds = new Rect(); + /** Offset relative to the window parent surface for {@link #mContentBounds}. */ + @NonNull + private final Point mContentRelOffset = new Point(); @NonNull final Transformation mTransformation = new Transformation(); @@ -78,6 +90,21 @@ class TaskFragmentAnimationAdapter { mTarget = target; mLeash = leash; mWholeAnimationBounds.set(wholeAnimationBounds); + if (target.mode == MODE_CLOSING) { + // When it is closing, we want to show the content at the start position in case the + // window is resizing as well. For example, when the activities is changing from split + // to stack, the bottom TaskFragment will be resized to fullscreen when hiding. + final Rect startBounds = target.startBounds; + final Rect endBounds = target.screenSpaceBounds; + mContentBounds.set(startBounds); + mContentRelOffset.set(target.localBounds.left, target.localBounds.top); + mContentRelOffset.offset( + startBounds.left - endBounds.left, + startBounds.top - endBounds.top); + } else { + mContentBounds.set(target.screenSpaceBounds); + mContentRelOffset.set(target.localBounds.left, target.localBounds.top); + } } /** @@ -108,8 +135,7 @@ class TaskFragmentAnimationAdapter { /** To be overridden by subclasses to adjust the animation surface change. */ void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) { // Update the surface position and alpha. - mTransformation.getMatrix().postTranslate( - mTarget.localBounds.left, mTarget.localBounds.top); + mTransformation.getMatrix().postTranslate(mContentRelOffset.x, mContentRelOffset.y); t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix); t.setAlpha(mLeash, mTransformation.getAlpha()); @@ -117,9 +143,8 @@ class TaskFragmentAnimationAdapter { // positionX/Y are in local coordinate, so minus the local offset to get the slide amount. final int positionX = Math.round(mMatrix[MTRANS_X]); final int positionY = Math.round(mMatrix[MTRANS_Y]); - final Rect cropRect = new Rect(mTarget.screenSpaceBounds); - final Rect localBounds = mTarget.localBounds; - cropRect.offset(positionX - localBounds.left, positionY - localBounds.top); + final Rect cropRect = new Rect(mContentBounds); + cropRect.offset(positionX - mContentRelOffset.x, positionY - mContentRelOffset.y); // Store the current offset of the surface top left from (0,0) in absolute coordinate. final int offsetX = cropRect.left; diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java index 0e13c59e593c8..322f85449450e 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java @@ -17,6 +17,7 @@ package androidx.window.extensions.embedding; import static android.os.Process.THREAD_PRIORITY_DISPLAY; +import static android.view.RemoteAnimationTarget.MODE_CHANGING; import static android.view.RemoteAnimationTarget.MODE_CLOSING; import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE; import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN; @@ -254,7 +255,7 @@ class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub { @NonNull RemoteAnimationTarget[] targets) { final List adapters = new ArrayList<>(); for (RemoteAnimationTarget target : targets) { - if (target.startBounds != null) { + if (target.mode == MODE_CHANGING) { // This is the target with bounds change. final Animation[] animations = mAnimationSpec.createChangeBoundsChangeAnimations(target); 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 13afa4910ae11..1f866c3b99c98 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java @@ -114,8 +114,8 @@ class TaskFragmentAnimationSpec { @NonNull Animation createChangeBoundsCloseAnimation(@NonNull RemoteAnimationTarget target) { final Rect parentBounds = target.taskInfo.configuration.windowConfiguration.getBounds(); - // TODO(b/258126915): we want to keep track of the closing start bounds - final Rect bounds = target.screenSpaceBounds; + // Use startBounds if the window is closing in case it may also resize. + final Rect bounds = target.startBounds; final int endTop; final int endLeft; if (parentBounds.top == bounds.top && parentBounds.bottom == bounds.bottom) { diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index 7e93109dcb6d4..abaa3630ff7b5 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -268,6 +268,7 @@ public class AppTransitionController { handleClosingApps(); handleOpeningApps(); handleChangingApps(transit); + handleClosingChangingContainers(); appTransition.setLastAppTransition(transit, topOpeningApp, topClosingApp, topChangingApp); @@ -287,6 +288,7 @@ public class AppTransitionController { mDisplayContent.mClosingApps.clear(); mDisplayContent.mChangingContainers.clear(); mDisplayContent.mUnknownAppVisibilityController.clear(); + mDisplayContent.mClosingChangingContainers.clear(); // This has changed the visibility of windows, so perform // a new layout to get them all up-to-date. @@ -1171,6 +1173,24 @@ public class AppTransitionController { } } + private void handleClosingChangingContainers() { + final ArrayMap containers = + mDisplayContent.mClosingChangingContainers; + while (!containers.isEmpty()) { + final WindowContainer container = containers.keyAt(0); + containers.remove(container); + + // For closing changing windows that are part of the transition, they should have been + // removed from mClosingChangingContainers in WindowContainer#getAnimationAdapter() + // If the closing changing TaskFragment is not part of the transition, update its + // surface after removing it from mClosingChangingContainers. + final TaskFragment taskFragment = container.asTaskFragment(); + if (taskFragment != null) { + taskFragment.updateOrganizedTaskFragmentSurface(); + } + } + } + private void handleChangingApps(@TransitionOldType int transit) { final ArraySet apps = mDisplayContent.mChangingContainers; final int appsCount = apps.size(); diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index b9aeec63daf0b..b33a83dac6e52 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -192,6 +192,7 @@ import android.os.Trace; import android.os.UserHandle; import android.os.WorkSource; import android.provider.Settings; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.DisplayMetrics; import android.util.DisplayUtils; @@ -348,6 +349,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final ArraySet mClosingApps = new ArraySet<>(); final ArraySet mChangingContainers = new ArraySet<>(); final UnknownAppVisibilityController mUnknownAppVisibilityController; + /** + * If a container is closing when resizing, keeps track of its starting bounds when it is + * removed from {@link #mChangingContainers}. + */ + final ArrayMap mClosingChangingContainers = new ArrayMap<>(); private MetricsLogger mMetricsLogger; diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java index 95371a52410f1..4c5d607ef7beb 100644 --- a/services/core/java/com/android/server/wm/RemoteAnimationController.java +++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java @@ -103,10 +103,29 @@ class RemoteAnimationController implements DeathRecipient { RemoteAnimationRecord createRemoteAnimationRecord(WindowContainer windowContainer, Point position, Rect localBounds, Rect endBounds, Rect startBounds, boolean showBackdrop) { + return createRemoteAnimationRecord(windowContainer, position, localBounds, endBounds, + startBounds, showBackdrop, startBounds != null /* shouldCreateSnapshot */); + } + + /** + * Creates an animation record for each individual {@link WindowContainer}. + * + * @param windowContainer The windows to animate. + * @param position The position app bounds relative to its parent. + * @param localBounds The bounds of the app relative to its parent. + * @param endBounds The end bounds after the transition, in screen coordinates. + * @param startBounds The start bounds before the transition, in screen coordinates. + * @param showBackdrop To show background behind a window during animation. + * @param shouldCreateSnapshot Whether this target should create a snapshot animation. + * @return The record representing animation(s) to run on the app. + */ + RemoteAnimationRecord createRemoteAnimationRecord(WindowContainer windowContainer, + Point position, Rect localBounds, Rect endBounds, Rect startBounds, + boolean showBackdrop, boolean shouldCreateSnapshot) { ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "createAnimationAdapter(): container=%s", windowContainer); final RemoteAnimationRecord adapters = new RemoteAnimationRecord(windowContainer, position, - localBounds, endBounds, startBounds, showBackdrop); + localBounds, endBounds, startBounds, showBackdrop, shouldCreateSnapshot); mPendingAnimations.add(adapters); return adapters; } @@ -438,14 +457,15 @@ class RemoteAnimationController implements DeathRecipient { private @RemoteAnimationTarget.Mode int mMode = RemoteAnimationTarget.MODE_CHANGING; RemoteAnimationRecord(WindowContainer windowContainer, Point endPos, Rect localBounds, - Rect endBounds, Rect startBounds, boolean showBackdrop) { + Rect endBounds, @Nullable Rect startBounds, boolean showBackdrop, + boolean shouldCreateSnapshot) { mWindowContainer = windowContainer; mShowBackdrop = showBackdrop; if (startBounds != null) { mStartBounds = new Rect(startBounds); mAdapter = new RemoteAnimationAdapterWrapper(this, endPos, localBounds, endBounds, mStartBounds, mShowBackdrop); - if (mRemoteAnimationAdapter.getChangeNeedsSnapshot()) { + if (shouldCreateSnapshot && mRemoteAnimationAdapter.getChangeNeedsSnapshot()) { 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 diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index cb254982a7807..f5a09e04fc4bc 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -2345,11 +2345,7 @@ class TaskFragment extends WindowContainer { @Override public void onConfigurationChanged(Configuration newParentConfig) { super.onConfigurationChanged(newParentConfig); - - if (mTaskFragmentOrganizer != null) { - updateOrganizedTaskFragmentSurface(); - } - + updateOrganizedTaskFragmentSurface(); sendTaskFragmentInfoChanged(); } @@ -2362,8 +2358,13 @@ class TaskFragment extends WindowContainer { updateOrganizedTaskFragmentSurface(); } - private void updateOrganizedTaskFragmentSurface() { - if (mDelayOrganizedTaskFragmentSurfaceUpdate) { + /** + * TaskFragmentOrganizer doesn't have access to the surface for security reasons, so we need to + * update its surface on the server side if it is not collected for Shell or in pending + * animation. + */ + void updateOrganizedTaskFragmentSurface() { + if (mDelayOrganizedTaskFragmentSurfaceUpdate || mTaskFragmentOrganizer == null) { return; } if (mTransitionController.isShellTransitionsEnabled() @@ -2395,7 +2396,10 @@ class TaskFragment extends WindowContainer { return; } - final Rect bounds = getBounds(); + // If this TaskFragment is closing while resizing, crop to the starting bounds instead. + final Rect bounds = isClosingWhenResizing() + ? mDisplayContent.mClosingChangingContainers.get(this) + : getBounds(); final int width = bounds.width(); final int height = bounds.height(); if (!forceUpdate && width == mLastSurfaceSize.x && height == mLastSurfaceSize.y) { @@ -2443,6 +2447,15 @@ class TaskFragment extends WindowContainer { || endBounds.height() != startBounds.height(); } + /** Records the starting bounds of the closing organized TaskFragment. */ + void setClosingChangingStartBoundsIfNeeded() { + if (isOrganizedTaskFragment() && mDisplayContent != null + && mDisplayContent.mChangingContainers.remove(this)) { + mDisplayContent.mClosingChangingContainers.put( + this, new Rect(mSurfaceFreezer.mFreezeBounds)); + } + } + @Override boolean isSyncFinished() { return super.isSyncFinished() && isReadyToTransit(); diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 80357eb0c7faa..0b80914272dd0 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -810,6 +810,7 @@ class WindowContainer extends ConfigurationContainer< void removeImmediately() { final DisplayContent dc = getDisplayContent(); if (dc != null) { + dc.mClosingChangingContainers.remove(this); mSurfaceFreezer.unfreeze(getSyncTransaction()); } while (!mChildren.isEmpty()) { @@ -1019,9 +1020,12 @@ class WindowContainer extends ConfigurationContainer< * @param dc The display this container is on after changes. */ void onDisplayChanged(DisplayContent dc) { - if (mDisplayContent != null && mDisplayContent.mChangingContainers.remove(this)) { - // Cancel any change transition queued-up for this container on the old display. - mSurfaceFreezer.unfreeze(getSyncTransaction()); + if (mDisplayContent != null) { + mDisplayContent.mClosingChangingContainers.remove(this); + if (mDisplayContent.mChangingContainers.remove(this)) { + // Cancel any change transition queued-up for this container on the old display. + mSurfaceFreezer.unfreeze(getSyncTransaction()); + } } mDisplayContent = dc; if (dc != null && dc != this) { @@ -1298,6 +1302,13 @@ class WindowContainer extends ConfigurationContainer< // If we are losing visibility, then a snapshot isn't necessary and we are no-longer // part of a change transition. if (!visible) { + if (asTaskFragment() != null) { + // If the organized TaskFragment is closing while resizing, we want to keep track of + // its starting bounds to make sure the animation starts at the correct position. + // This should be called before unfreeze() because we record the starting bounds + // in SurfaceFreezer. + asTaskFragment().setClosingChangingStartBoundsIfNeeded(); + } mSurfaceFreezer.unfreeze(getSyncTransaction()); } WindowContainer parent = getParent(); @@ -1306,6 +1317,12 @@ class WindowContainer extends ConfigurationContainer< } } + /** Whether this window is closing while resizing. */ + boolean isClosingWhenResizing() { + return mDisplayContent != null + && mDisplayContent.mClosingChangingContainers.containsKey(this); + } + void writeIdentifierToProto(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); proto.write(HASH_CODE, System.identityHashCode(this)); @@ -3004,10 +3021,22 @@ class WindowContainer extends ConfigurationContainer< } final Rect localBounds = new Rect(mTmpRect); localBounds.offsetTo(mTmpPoint.x, mTmpPoint.y); - final RemoteAnimationController.RemoteAnimationRecord adapters = - controller.createRemoteAnimationRecord( - this, mTmpPoint, localBounds, screenBounds, - (isChanging ? mSurfaceFreezer.mFreezeBounds : null), showBackdrop); + final RemoteAnimationController.RemoteAnimationRecord adapters; + if (!isChanging && !enter && isClosingWhenResizing()) { + // Container that is closing while resizing. Pass in the closing start bounds, so + // the animation can start with the correct bounds, there won't be a snapshot. + // Cleanup the mClosingChangingContainers so that when the animation is finished, it + // will reset the surface. + final Rect closingStartBounds = getDisplayContent().mClosingChangingContainers + .remove(this); + adapters = controller.createRemoteAnimationRecord( + this, mTmpPoint, localBounds, screenBounds, closingStartBounds, + showBackdrop, false /* shouldCreateSnapshot */); + } else { + final Rect startBounds = isChanging ? mSurfaceFreezer.mFreezeBounds : null; + adapters = controller.createRemoteAnimationRecord( + this, mTmpPoint, localBounds, screenBounds, startBounds, showBackdrop); + } if (backdropColor != 0) { adapters.setBackDropColor(backdropColor); } @@ -3464,7 +3493,13 @@ class WindowContainer extends ConfigurationContainer< return; } - getRelativePosition(mTmpPos); + if (isClosingWhenResizing()) { + // This container is closing while resizing, keep its surface at the starting position + // to prevent animation flicker. + getRelativePosition(mDisplayContent.mClosingChangingContainers.get(this), mTmpPos); + } else { + getRelativePosition(mTmpPos); + } final int deltaRotation = getRelativeDisplayRotation(); if (mTmpPos.equals(mLastSurfacePosition) && deltaRotation == mLastDeltaRotation) { return; @@ -3529,9 +3564,14 @@ class WindowContainer extends ConfigurationContainer< outSurfaceInsets.setEmpty(); } + /** Gets the position of this container in its parent's coordinate. */ void getRelativePosition(Point outPos) { - final Rect dispBounds = getBounds(); - outPos.set(dispBounds.left, dispBounds.top); + getRelativePosition(getBounds(), outPos); + } + + /** Gets the position of {@code curBounds} in this container's parent's coordinate. */ + void getRelativePosition(Rect curBounds, Point outPos) { + outPos.set(curBounds.left, curBounds.top); final WindowContainer parent = getParent(); if (parent != null) { final Rect parentBounds = parent.getBounds();