Loading core/java/android/window/TransitionInfo.java +5 −1 Original line number Diff line number Diff line Loading @@ -159,8 +159,11 @@ public final class TransitionInfo implements Parcelable { */ public static final int FLAG_SYNC = 1 << 21; /** This change represents its start configuration for the duration of the animation. */ public static final int FLAG_CONFIG_AT_END = 1 << 22; /** The first unused bit. This can be used by remotes to attach custom flags to this change. */ public static final int FLAG_FIRST_CUSTOM = 1 << 22; public static final int FLAG_FIRST_CUSTOM = 1 << 23; /** The change belongs to a window that won't contain activities. */ public static final int FLAGS_IS_NON_APP_WINDOW = Loading Loading @@ -193,6 +196,7 @@ public final class TransitionInfo implements Parcelable { FLAG_TASK_LAUNCHING_BEHIND, FLAG_MOVED_TO_TOP, FLAG_SYNC, FLAG_CONFIG_AT_END, FLAG_FIRST_CUSTOM }) public @interface ChangeFlags {} Loading core/java/android/window/WindowContainerTransaction.java +30 −0 Original line number Diff line number Diff line Loading @@ -913,6 +913,23 @@ public final class WindowContainerTransaction implements Parcelable { return this; } /** * Defers client-facing configuration changes for activities in `container` until the end of * the transition animation. The configuration will still be applied to the WMCore hierarchy * at the normal time (beginning); so, special consideration must be made for this in the * animation. * * @param container WindowContainerToken who's children should defer config notification. * @hide */ @NonNull public WindowContainerTransaction deferConfigToTransitionEnd( @NonNull WindowContainerToken container) { final Change change = getOrCreateChange(container.asBinder()); change.mConfigAtTransitionEnd = true; return this; } /** * Merges another WCT into this one. * @param transfer When true, this will transfer everything from other potentially leaving Loading Loading @@ -1050,6 +1067,7 @@ public final class WindowContainerTransaction implements Parcelable { private Rect mBoundsChangeSurfaceBounds = null; @Nullable private Rect mRelativeBounds = null; private boolean mConfigAtTransitionEnd = false; private int mActivityWindowingMode = -1; private int mWindowingMode = -1; Loading Loading @@ -1082,6 +1100,7 @@ public final class WindowContainerTransaction implements Parcelable { mRelativeBounds = new Rect(); mRelativeBounds.readFromParcel(in); } mConfigAtTransitionEnd = in.readBoolean(); mWindowingMode = in.readInt(); mActivityWindowingMode = in.readInt(); Loading Loading @@ -1134,6 +1153,8 @@ public final class WindowContainerTransaction implements Parcelable { ? other.mRelativeBounds : new Rect(other.mRelativeBounds); } mConfigAtTransitionEnd = mConfigAtTransitionEnd || other.mConfigAtTransitionEnd; } public int getWindowingMode() { Loading Loading @@ -1191,6 +1212,11 @@ public final class WindowContainerTransaction implements Parcelable { return mDragResizing; } /** Gets whether the config should be sent to the client at the end of the transition. */ public boolean getConfigAtTransitionEnd() { return mConfigAtTransitionEnd; } public int getChangeMask() { return mChangeMask; } Loading Loading @@ -1269,6 +1295,9 @@ public final class WindowContainerTransaction implements Parcelable { if ((mChangeMask & CHANGE_RELATIVE_BOUNDS) != 0) { sb.append("relativeBounds:").append(mRelativeBounds).append(","); } if (mConfigAtTransitionEnd) { sb.append("configAtTransitionEnd").append(","); } sb.append("}"); return sb.toString(); } Loading Loading @@ -1297,6 +1326,7 @@ public final class WindowContainerTransaction implements Parcelable { if (mRelativeBounds != null) { mRelativeBounds.writeToParcel(dest, flags); } dest.writeBoolean(mConfigAtTransitionEnd); dest.writeInt(mWindowingMode); dest.writeInt(mActivityWindowingMode); Loading services/core/java/com/android/server/wm/Transition.java +157 −47 Original line number Diff line number Diff line Loading @@ -44,6 +44,7 @@ import static android.view.WindowManager.TransitionType; import static android.view.WindowManager.transitTypeToString; import static android.window.TaskFragmentAnimationParams.DEFAULT_ANIMATION_BACKGROUND_COLOR; import static android.window.TransitionInfo.FLAGS_IS_OCCLUDED_NO_ANIMATION; import static android.window.TransitionInfo.FLAG_CONFIG_AT_END; import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS; import static android.window.TransitionInfo.FLAG_FILLS_TASK; import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; Loading @@ -65,6 +66,7 @@ import static com.android.server.wm.ActivityRecord.State.RESUMED; import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_RECENTS_ANIM; import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_SPLASH_SCREEN; import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_WINDOWS_DRAWN; import static com.android.server.wm.WindowState.BLAST_TIMEOUT_DURATION; import android.annotation.IntDef; import android.annotation.NonNull; Loading Loading @@ -307,6 +309,12 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { */ int mAnimationTrack = 0; /** * List of activities whose configurations are sent to the client at the end of the transition * instead of immediately when the configuration changes. */ ArrayList<ActivityRecord> mConfigAtEndActivities = null; Transition(@TransitionType int type, @TransitionFlags int flags, TransitionController controller, BLASTSyncEngine syncEngine) { mType = type; Loading Loading @@ -484,6 +492,22 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { return mTargetDisplays.contains(dc); } void setConfigAtEnd(@NonNull WindowContainer<?> wc) { wc.forAllActivities(ar -> { if (!ar.isVisible() || !ar.isVisibleRequested()) return; if (mConfigAtEndActivities == null) { mConfigAtEndActivities = new ArrayList<>(); } if (mConfigAtEndActivities.contains(ar)) { return; } mConfigAtEndActivities.add(ar); ar.pauseConfigurationDispatch(); }); snapshotStartState(wc); mChanges.get(wc).mFlags |= ChangeInfo.FLAG_CHANGE_CONFIG_AT_END; } /** Set a transition to be a seamless-rotation. */ void setSeamlessRotation(@NonNull WindowContainer wc) { final ChangeInfo info = mChanges.get(wc); Loading Loading @@ -644,20 +668,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Collecting in transition %d: %s", mSyncId, wc); // "snapshot" all parents (as potential promotion targets). Do this before checking // if this is already a participant in case it has since been re-parented. for (WindowContainer<?> curr = getAnimatableParent(wc); curr != null && !mChanges.containsKey(curr); curr = getAnimatableParent(curr)) { final ChangeInfo info = new ChangeInfo(curr); updateTransientFlags(info); mChanges.put(curr, info); if (isReadyGroup(curr)) { mReadyTrackerOld.addGroup(curr); ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Creating Ready-group for" + " Transition %d with root=%s", mSyncId, curr); } } // Snapshot before checking if this is a participant in case it has been re-parented. snapshotStartState(getAnimatableParent(wc)); if (mParticipants.contains(wc)) return; // Transient-hide may be hidden later, so no need to request redraw. if (!isInTransientHide(wc)) { Loading Loading @@ -688,6 +700,22 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } } /** "snapshot" `wc` and all its parents (as potential promotion targets). */ private void snapshotStartState(@NonNull WindowContainer<?> wc) { for (WindowContainer<?> curr = wc; curr != null && !mChanges.containsKey(curr); curr = getAnimatableParent(curr)) { final ChangeInfo info = new ChangeInfo(curr); updateTransientFlags(info); mChanges.put(curr, info); if (isReadyGroup(curr)) { mReadyTrackerOld.addGroup(curr); ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Creating Ready-group for" + " Transition %d with root=%s", mSyncId, curr); } } } private void updateTransientFlags(@NonNull ChangeInfo info) { final WindowContainer<?> wc = info.mContainer; // Only look at tasks, taskfragments, or activities Loading Loading @@ -934,23 +962,12 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } /** * Build a transaction that "resets" all the re-parenting and layer changes. This is * intended to be applied at the end of the transition but before the finish callback. This * needs to be passed/applied in shell because until finish is called, shell owns the surfaces. * Additionally, this gives shell the ability to better deal with merged transitions. * Populates `t` with instructions to reset surface transform of `change` so it matches * the WM hierarchy. This "undoes" lingering state left by the animation. */ private void buildFinishTransaction(SurfaceControl.Transaction t, TransitionInfo info) { private void resetSurfaceTransform(SurfaceControl.Transaction t, WindowContainer target, SurfaceControl targetLeash) { final Point tmpPos = new Point(); // usually only size 1 final ArraySet<DisplayContent> displays = new ArraySet<>(); for (int i = mTargets.size() - 1; i >= 0; --i) { final WindowContainer target = mTargets.get(i).mContainer; if (target.getParent() != null) { final SurfaceControl targetLeash = getLeashSurface(target, null /* t */); final SurfaceControl origParent = getOrigParentSurface(target); // Ensure surfaceControls are re-parented back into the hierarchy. t.reparent(targetLeash, origParent); t.setLayer(targetLeash, target.getLastLayer()); target.getRelativePosition(tmpPos); t.setPosition(targetLeash, tmpPos.x, tmpPos.y); // No need to clip the display in case seeing the clipped content when during the Loading @@ -963,10 +980,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { final Rect clipRect = target.getResolvedOverrideBounds(); t.setWindowCrop(targetLeash, clipRect.width(), clipRect.height()); } t.setCornerRadius(targetLeash, 0); t.setShadowRadius(targetLeash, 0); t.setMatrix(targetLeash, 1, 0, 0, 1); t.setAlpha(targetLeash, 1); // The bounds sent to the transition is always a real bounds. This means we lose // information about "null" bounds (inheriting from parent). Core will fix-up // non-organized window surface bounds; however, since Core can't touch organized Loading @@ -974,7 +988,34 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if (target.isOrganized() && target.matchParentBounds()) { t.setWindowCrop(targetLeash, -1, -1); } } /** * Build a transaction that "resets" all the re-parenting and layer changes. This is * intended to be applied at the end of the transition but before the finish callback. This * needs to be passed/applied in shell because until finish is called, shell owns the surfaces. * Additionally, this gives shell the ability to better deal with merged transitions. */ private void buildFinishTransaction(SurfaceControl.Transaction t, TransitionInfo info) { // usually only size 1 final ArraySet<DisplayContent> displays = new ArraySet<>(); for (int i = mTargets.size() - 1; i >= 0; --i) { final WindowContainer<?> target = mTargets.get(i).mContainer; if (target.getParent() == null) continue; final SurfaceControl targetLeash = getLeashSurface(target, null /* t */); final SurfaceControl origParent = getOrigParentSurface(target); // Ensure surfaceControls are re-parented back into the hierarchy. t.reparent(targetLeash, origParent); t.setLayer(targetLeash, target.getLastLayer()); t.setCornerRadius(targetLeash, 0); t.setShadowRadius(targetLeash, 0); t.setAlpha(targetLeash, 1); displays.add(target.getDisplayContent()); // For config-at-end, the end-transform will be reset after the config is actually // applied in the client (since the transform depends on config). The other properties // remain here because shell might want to persistently override them. if ((mTargets.get(i).mFlags & ChangeInfo.FLAG_CHANGE_CONFIG_AT_END) == 0) { resetSurfaceTransform(t, target, targetLeash); } } // Remove screenshot layers if necessary Loading Loading @@ -1304,6 +1345,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { mController.mAtm.mRootWindowContainer.rankTaskLayers(); } commitConfigAtEndActivities(); // dispatch legacy callback in a different loop. This is because multiple legacy handlers // (fixed-rotation/displaycontent) make global changes, so we want to ensure that we've // processed all the participants first (in particular, we want to trigger pip-enter first) Loading Loading @@ -1421,6 +1464,52 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { mController.updateAnimatingState(); } private void commitConfigAtEndActivities() { if (mConfigAtEndActivities == null || mConfigAtEndActivities.isEmpty()) { return; } final SurfaceControl.Transaction t = mController.mAtm.mWindowManager.mTransactionFactory.get(); for (int i = 0; i < mTargets.size(); ++i) { final WindowContainer target = mTargets.get(i).mContainer; if (target.getParent() == null || (mTargets.get(i).mFlags & ChangeInfo.FLAG_CHANGE_CONFIG_AT_END) == 0) { continue; } final SurfaceControl targetLeash = getLeashSurface(target, null /* t */); // Reset surface state here (since it was skipped in buildFinishTransaction). Since // we are resuming config to the "current" state, we have to calculate the matching // surface state now (rather than snapshotting it at animation start). resetSurfaceTransform(t, target, targetLeash); } // Now we resume the configuration dispatch, wait until the now resumed configs have been // drawn, and then apply everything together. final BLASTSyncEngine.SyncGroup sg = mSyncEngine.prepareSyncSet( new BLASTSyncEngine.TransactionReadyListener() { @Override public void onTransactionReady(int mSyncId, SurfaceControl.Transaction transaction) { t.merge(transaction); t.apply(); } @Override public void onTransactionCommitTimeout() { t.apply(); } }, "ConfigAtTransitEnd"); final int syncId = sg.mSyncId; mSyncEngine.startSyncSet(sg, BLAST_TIMEOUT_DURATION, true /* parallel */); mSyncEngine.setSyncMethod(syncId, BLASTSyncEngine.METHOD_BLAST); for (int i = 0; i < mConfigAtEndActivities.size(); ++i) { final ActivityRecord ar = mConfigAtEndActivities.get(i); mSyncEngine.addToSyncSet(syncId, ar); ar.resumeConfigurationDispatch(); } mSyncEngine.setReady(syncId); } @Nullable private ActivityRecord getVisibleTransientLaunch(TaskDisplayArea taskDisplayArea) { if (mTransientLaunches == null) return null; Loading Loading @@ -1546,6 +1635,12 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if (mState == STATE_ABORT) { mController.onAbort(this); if (mConfigAtEndActivities != null) { for (int i = 0; i < mConfigAtEndActivities.size(); ++i) { mConfigAtEndActivities.get(i).resumeConfigurationDispatch(); } mConfigAtEndActivities = null; } primaryDisplay.getPendingTransaction().merge(transaction); mSyncId = -1; mOverrideOptions = null; Loading Loading @@ -2291,6 +2386,11 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } else { parentChange.mFlags |= ChangeInfo.FLAG_CHANGE_YES_ANIMATION; } final ActivityRecord ar = targetChange.mContainer.asActivityRecord(); if ((ar != null && ar.isConfigurationDispatchPaused()) || ((targetChange.mFlags & ChangeInfo.FLAG_CHANGE_CONFIG_AT_END) != 0)) { parentChange.mFlags |= ChangeInfo.FLAG_CHANGE_CONFIG_AT_END; } } } Loading Loading @@ -2940,6 +3040,9 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { /** Whether this change's container moved to the top. */ private static final int FLAG_CHANGE_MOVED_TO_TOP = 0x20; /** Whether this change contains config-at-end members. */ private static final int FLAG_CHANGE_CONFIG_AT_END = 0x40; @IntDef(prefix = { "FLAG_" }, value = { FLAG_NONE, FLAG_SEAMLESS_ROTATION, Loading @@ -2947,7 +3050,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { FLAG_ABOVE_TRANSIENT_LAUNCH, FLAG_CHANGE_NO_ANIMATION, FLAG_CHANGE_YES_ANIMATION, FLAG_CHANGE_MOVED_TO_TOP FLAG_CHANGE_MOVED_TO_TOP, FLAG_CHANGE_CONFIG_AT_END }) @Retention(RetentionPolicy.SOURCE) @interface Flag {} Loading Loading @@ -3095,6 +3199,9 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { flags |= FLAG_IS_VOICE_INTERACTION; } flags |= record.mTransitionChangeFlags; if (record.isConfigurationDispatchPaused()) { flags |= FLAG_CONFIG_AT_END; } } final TaskFragment taskFragment = wc.asTaskFragment(); if (taskFragment != null && task == null) { Loading Loading @@ -3140,6 +3247,9 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if ((mFlags & FLAG_CHANGE_MOVED_TO_TOP) != 0) { flags |= FLAG_MOVED_TO_TOP; } if ((mFlags & FLAG_CHANGE_CONFIG_AT_END) != 0) { flags |= FLAG_CONFIG_AT_END; } return flags; } Loading services/core/java/com/android/server/wm/WindowOrganizerController.java +15 −2 Original line number Diff line number Diff line Loading @@ -583,8 +583,21 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps(); final int hopSize = hops.size(); Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries = t.getChanges().entrySet().iterator(); Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries; if (transition != null) { // Mark any config-at-end containers before applying config changes so that // the config changes don't dispatch to client. entries = t.getChanges().entrySet().iterator(); while (entries.hasNext()) { final Map.Entry<IBinder, WindowContainerTransaction.Change> entry = entries.next(); if (!entry.getValue().getConfigAtTransitionEnd()) continue; final WindowContainer wc = WindowContainer.fromBinder(entry.getKey()); if (wc == null || !wc.isAttached()) continue; transition.setConfigAtEnd(wc); } } entries = t.getChanges().entrySet().iterator(); while (entries.hasNext()) { final Map.Entry<IBinder, WindowContainerTransaction.Change> entry = entries.next(); final WindowContainer wc = WindowContainer.fromBinder(entry.getKey()); Loading services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +31 −0 Original line number Diff line number Diff line Loading @@ -33,6 +33,7 @@ import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.window.TransitionInfo.FLAG_CONFIG_AT_END; import static android.window.TransitionInfo.FLAG_FILLS_TASK; import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW; Loading Loading @@ -2539,6 +2540,36 @@ public class TransitionTests extends WindowTestsBase { } } @Test public void testConfigAtEnd() { final TransitionController controller = mDisplayContent.mTransitionController; Transition transit = createTestTransition(TRANSIT_CHANGE, controller); final TestTransitionPlayer player = registerTestTransitionPlayer(); final Task task = createTask(mDisplayContent); final Rect taskBounds = new Rect(0, 0, 200, 300); task.getConfiguration().windowConfiguration.setBounds(taskBounds); final ActivityRecord activity = createActivityRecord(task); activity.setVisibleRequested(true); activity.setVisible(true); controller.moveToCollecting(transit); transit.collect(task); transit.setConfigAtEnd(task); task.getRequestedOverrideConfiguration().windowConfiguration.setBounds( new Rect(10, 10, 200, 300)); task.onRequestedOverrideConfigurationChanged(task.getRequestedOverrideConfiguration()); controller.requestStartTransition(transit, task, null, null); player.start(); assertTrue(activity.isConfigurationDispatchPaused()); // config-at-end flag must propagate up to task if activity was promoted. assertTrue(player.mLastReady.getChange( task.mRemoteToken.toWindowContainerToken()).hasFlags(FLAG_CONFIG_AT_END)); player.finish(); assertFalse(activity.isConfigurationDispatchPaused()); } @Test public void testReadyTrackerBasics() { final TransitionController controller = new TestTransitionController( Loading Loading
core/java/android/window/TransitionInfo.java +5 −1 Original line number Diff line number Diff line Loading @@ -159,8 +159,11 @@ public final class TransitionInfo implements Parcelable { */ public static final int FLAG_SYNC = 1 << 21; /** This change represents its start configuration for the duration of the animation. */ public static final int FLAG_CONFIG_AT_END = 1 << 22; /** The first unused bit. This can be used by remotes to attach custom flags to this change. */ public static final int FLAG_FIRST_CUSTOM = 1 << 22; public static final int FLAG_FIRST_CUSTOM = 1 << 23; /** The change belongs to a window that won't contain activities. */ public static final int FLAGS_IS_NON_APP_WINDOW = Loading Loading @@ -193,6 +196,7 @@ public final class TransitionInfo implements Parcelable { FLAG_TASK_LAUNCHING_BEHIND, FLAG_MOVED_TO_TOP, FLAG_SYNC, FLAG_CONFIG_AT_END, FLAG_FIRST_CUSTOM }) public @interface ChangeFlags {} Loading
core/java/android/window/WindowContainerTransaction.java +30 −0 Original line number Diff line number Diff line Loading @@ -913,6 +913,23 @@ public final class WindowContainerTransaction implements Parcelable { return this; } /** * Defers client-facing configuration changes for activities in `container` until the end of * the transition animation. The configuration will still be applied to the WMCore hierarchy * at the normal time (beginning); so, special consideration must be made for this in the * animation. * * @param container WindowContainerToken who's children should defer config notification. * @hide */ @NonNull public WindowContainerTransaction deferConfigToTransitionEnd( @NonNull WindowContainerToken container) { final Change change = getOrCreateChange(container.asBinder()); change.mConfigAtTransitionEnd = true; return this; } /** * Merges another WCT into this one. * @param transfer When true, this will transfer everything from other potentially leaving Loading Loading @@ -1050,6 +1067,7 @@ public final class WindowContainerTransaction implements Parcelable { private Rect mBoundsChangeSurfaceBounds = null; @Nullable private Rect mRelativeBounds = null; private boolean mConfigAtTransitionEnd = false; private int mActivityWindowingMode = -1; private int mWindowingMode = -1; Loading Loading @@ -1082,6 +1100,7 @@ public final class WindowContainerTransaction implements Parcelable { mRelativeBounds = new Rect(); mRelativeBounds.readFromParcel(in); } mConfigAtTransitionEnd = in.readBoolean(); mWindowingMode = in.readInt(); mActivityWindowingMode = in.readInt(); Loading Loading @@ -1134,6 +1153,8 @@ public final class WindowContainerTransaction implements Parcelable { ? other.mRelativeBounds : new Rect(other.mRelativeBounds); } mConfigAtTransitionEnd = mConfigAtTransitionEnd || other.mConfigAtTransitionEnd; } public int getWindowingMode() { Loading Loading @@ -1191,6 +1212,11 @@ public final class WindowContainerTransaction implements Parcelable { return mDragResizing; } /** Gets whether the config should be sent to the client at the end of the transition. */ public boolean getConfigAtTransitionEnd() { return mConfigAtTransitionEnd; } public int getChangeMask() { return mChangeMask; } Loading Loading @@ -1269,6 +1295,9 @@ public final class WindowContainerTransaction implements Parcelable { if ((mChangeMask & CHANGE_RELATIVE_BOUNDS) != 0) { sb.append("relativeBounds:").append(mRelativeBounds).append(","); } if (mConfigAtTransitionEnd) { sb.append("configAtTransitionEnd").append(","); } sb.append("}"); return sb.toString(); } Loading Loading @@ -1297,6 +1326,7 @@ public final class WindowContainerTransaction implements Parcelable { if (mRelativeBounds != null) { mRelativeBounds.writeToParcel(dest, flags); } dest.writeBoolean(mConfigAtTransitionEnd); dest.writeInt(mWindowingMode); dest.writeInt(mActivityWindowingMode); Loading
services/core/java/com/android/server/wm/Transition.java +157 −47 Original line number Diff line number Diff line Loading @@ -44,6 +44,7 @@ import static android.view.WindowManager.TransitionType; import static android.view.WindowManager.transitTypeToString; import static android.window.TaskFragmentAnimationParams.DEFAULT_ANIMATION_BACKGROUND_COLOR; import static android.window.TransitionInfo.FLAGS_IS_OCCLUDED_NO_ANIMATION; import static android.window.TransitionInfo.FLAG_CONFIG_AT_END; import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS; import static android.window.TransitionInfo.FLAG_FILLS_TASK; import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; Loading @@ -65,6 +66,7 @@ import static com.android.server.wm.ActivityRecord.State.RESUMED; import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_RECENTS_ANIM; import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_SPLASH_SCREEN; import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_WINDOWS_DRAWN; import static com.android.server.wm.WindowState.BLAST_TIMEOUT_DURATION; import android.annotation.IntDef; import android.annotation.NonNull; Loading Loading @@ -307,6 +309,12 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { */ int mAnimationTrack = 0; /** * List of activities whose configurations are sent to the client at the end of the transition * instead of immediately when the configuration changes. */ ArrayList<ActivityRecord> mConfigAtEndActivities = null; Transition(@TransitionType int type, @TransitionFlags int flags, TransitionController controller, BLASTSyncEngine syncEngine) { mType = type; Loading Loading @@ -484,6 +492,22 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { return mTargetDisplays.contains(dc); } void setConfigAtEnd(@NonNull WindowContainer<?> wc) { wc.forAllActivities(ar -> { if (!ar.isVisible() || !ar.isVisibleRequested()) return; if (mConfigAtEndActivities == null) { mConfigAtEndActivities = new ArrayList<>(); } if (mConfigAtEndActivities.contains(ar)) { return; } mConfigAtEndActivities.add(ar); ar.pauseConfigurationDispatch(); }); snapshotStartState(wc); mChanges.get(wc).mFlags |= ChangeInfo.FLAG_CHANGE_CONFIG_AT_END; } /** Set a transition to be a seamless-rotation. */ void setSeamlessRotation(@NonNull WindowContainer wc) { final ChangeInfo info = mChanges.get(wc); Loading Loading @@ -644,20 +668,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Collecting in transition %d: %s", mSyncId, wc); // "snapshot" all parents (as potential promotion targets). Do this before checking // if this is already a participant in case it has since been re-parented. for (WindowContainer<?> curr = getAnimatableParent(wc); curr != null && !mChanges.containsKey(curr); curr = getAnimatableParent(curr)) { final ChangeInfo info = new ChangeInfo(curr); updateTransientFlags(info); mChanges.put(curr, info); if (isReadyGroup(curr)) { mReadyTrackerOld.addGroup(curr); ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Creating Ready-group for" + " Transition %d with root=%s", mSyncId, curr); } } // Snapshot before checking if this is a participant in case it has been re-parented. snapshotStartState(getAnimatableParent(wc)); if (mParticipants.contains(wc)) return; // Transient-hide may be hidden later, so no need to request redraw. if (!isInTransientHide(wc)) { Loading Loading @@ -688,6 +700,22 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } } /** "snapshot" `wc` and all its parents (as potential promotion targets). */ private void snapshotStartState(@NonNull WindowContainer<?> wc) { for (WindowContainer<?> curr = wc; curr != null && !mChanges.containsKey(curr); curr = getAnimatableParent(curr)) { final ChangeInfo info = new ChangeInfo(curr); updateTransientFlags(info); mChanges.put(curr, info); if (isReadyGroup(curr)) { mReadyTrackerOld.addGroup(curr); ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Creating Ready-group for" + " Transition %d with root=%s", mSyncId, curr); } } } private void updateTransientFlags(@NonNull ChangeInfo info) { final WindowContainer<?> wc = info.mContainer; // Only look at tasks, taskfragments, or activities Loading Loading @@ -934,23 +962,12 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } /** * Build a transaction that "resets" all the re-parenting and layer changes. This is * intended to be applied at the end of the transition but before the finish callback. This * needs to be passed/applied in shell because until finish is called, shell owns the surfaces. * Additionally, this gives shell the ability to better deal with merged transitions. * Populates `t` with instructions to reset surface transform of `change` so it matches * the WM hierarchy. This "undoes" lingering state left by the animation. */ private void buildFinishTransaction(SurfaceControl.Transaction t, TransitionInfo info) { private void resetSurfaceTransform(SurfaceControl.Transaction t, WindowContainer target, SurfaceControl targetLeash) { final Point tmpPos = new Point(); // usually only size 1 final ArraySet<DisplayContent> displays = new ArraySet<>(); for (int i = mTargets.size() - 1; i >= 0; --i) { final WindowContainer target = mTargets.get(i).mContainer; if (target.getParent() != null) { final SurfaceControl targetLeash = getLeashSurface(target, null /* t */); final SurfaceControl origParent = getOrigParentSurface(target); // Ensure surfaceControls are re-parented back into the hierarchy. t.reparent(targetLeash, origParent); t.setLayer(targetLeash, target.getLastLayer()); target.getRelativePosition(tmpPos); t.setPosition(targetLeash, tmpPos.x, tmpPos.y); // No need to clip the display in case seeing the clipped content when during the Loading @@ -963,10 +980,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { final Rect clipRect = target.getResolvedOverrideBounds(); t.setWindowCrop(targetLeash, clipRect.width(), clipRect.height()); } t.setCornerRadius(targetLeash, 0); t.setShadowRadius(targetLeash, 0); t.setMatrix(targetLeash, 1, 0, 0, 1); t.setAlpha(targetLeash, 1); // The bounds sent to the transition is always a real bounds. This means we lose // information about "null" bounds (inheriting from parent). Core will fix-up // non-organized window surface bounds; however, since Core can't touch organized Loading @@ -974,7 +988,34 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if (target.isOrganized() && target.matchParentBounds()) { t.setWindowCrop(targetLeash, -1, -1); } } /** * Build a transaction that "resets" all the re-parenting and layer changes. This is * intended to be applied at the end of the transition but before the finish callback. This * needs to be passed/applied in shell because until finish is called, shell owns the surfaces. * Additionally, this gives shell the ability to better deal with merged transitions. */ private void buildFinishTransaction(SurfaceControl.Transaction t, TransitionInfo info) { // usually only size 1 final ArraySet<DisplayContent> displays = new ArraySet<>(); for (int i = mTargets.size() - 1; i >= 0; --i) { final WindowContainer<?> target = mTargets.get(i).mContainer; if (target.getParent() == null) continue; final SurfaceControl targetLeash = getLeashSurface(target, null /* t */); final SurfaceControl origParent = getOrigParentSurface(target); // Ensure surfaceControls are re-parented back into the hierarchy. t.reparent(targetLeash, origParent); t.setLayer(targetLeash, target.getLastLayer()); t.setCornerRadius(targetLeash, 0); t.setShadowRadius(targetLeash, 0); t.setAlpha(targetLeash, 1); displays.add(target.getDisplayContent()); // For config-at-end, the end-transform will be reset after the config is actually // applied in the client (since the transform depends on config). The other properties // remain here because shell might want to persistently override them. if ((mTargets.get(i).mFlags & ChangeInfo.FLAG_CHANGE_CONFIG_AT_END) == 0) { resetSurfaceTransform(t, target, targetLeash); } } // Remove screenshot layers if necessary Loading Loading @@ -1304,6 +1345,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { mController.mAtm.mRootWindowContainer.rankTaskLayers(); } commitConfigAtEndActivities(); // dispatch legacy callback in a different loop. This is because multiple legacy handlers // (fixed-rotation/displaycontent) make global changes, so we want to ensure that we've // processed all the participants first (in particular, we want to trigger pip-enter first) Loading Loading @@ -1421,6 +1464,52 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { mController.updateAnimatingState(); } private void commitConfigAtEndActivities() { if (mConfigAtEndActivities == null || mConfigAtEndActivities.isEmpty()) { return; } final SurfaceControl.Transaction t = mController.mAtm.mWindowManager.mTransactionFactory.get(); for (int i = 0; i < mTargets.size(); ++i) { final WindowContainer target = mTargets.get(i).mContainer; if (target.getParent() == null || (mTargets.get(i).mFlags & ChangeInfo.FLAG_CHANGE_CONFIG_AT_END) == 0) { continue; } final SurfaceControl targetLeash = getLeashSurface(target, null /* t */); // Reset surface state here (since it was skipped in buildFinishTransaction). Since // we are resuming config to the "current" state, we have to calculate the matching // surface state now (rather than snapshotting it at animation start). resetSurfaceTransform(t, target, targetLeash); } // Now we resume the configuration dispatch, wait until the now resumed configs have been // drawn, and then apply everything together. final BLASTSyncEngine.SyncGroup sg = mSyncEngine.prepareSyncSet( new BLASTSyncEngine.TransactionReadyListener() { @Override public void onTransactionReady(int mSyncId, SurfaceControl.Transaction transaction) { t.merge(transaction); t.apply(); } @Override public void onTransactionCommitTimeout() { t.apply(); } }, "ConfigAtTransitEnd"); final int syncId = sg.mSyncId; mSyncEngine.startSyncSet(sg, BLAST_TIMEOUT_DURATION, true /* parallel */); mSyncEngine.setSyncMethod(syncId, BLASTSyncEngine.METHOD_BLAST); for (int i = 0; i < mConfigAtEndActivities.size(); ++i) { final ActivityRecord ar = mConfigAtEndActivities.get(i); mSyncEngine.addToSyncSet(syncId, ar); ar.resumeConfigurationDispatch(); } mSyncEngine.setReady(syncId); } @Nullable private ActivityRecord getVisibleTransientLaunch(TaskDisplayArea taskDisplayArea) { if (mTransientLaunches == null) return null; Loading Loading @@ -1546,6 +1635,12 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if (mState == STATE_ABORT) { mController.onAbort(this); if (mConfigAtEndActivities != null) { for (int i = 0; i < mConfigAtEndActivities.size(); ++i) { mConfigAtEndActivities.get(i).resumeConfigurationDispatch(); } mConfigAtEndActivities = null; } primaryDisplay.getPendingTransaction().merge(transaction); mSyncId = -1; mOverrideOptions = null; Loading Loading @@ -2291,6 +2386,11 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } else { parentChange.mFlags |= ChangeInfo.FLAG_CHANGE_YES_ANIMATION; } final ActivityRecord ar = targetChange.mContainer.asActivityRecord(); if ((ar != null && ar.isConfigurationDispatchPaused()) || ((targetChange.mFlags & ChangeInfo.FLAG_CHANGE_CONFIG_AT_END) != 0)) { parentChange.mFlags |= ChangeInfo.FLAG_CHANGE_CONFIG_AT_END; } } } Loading Loading @@ -2940,6 +3040,9 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { /** Whether this change's container moved to the top. */ private static final int FLAG_CHANGE_MOVED_TO_TOP = 0x20; /** Whether this change contains config-at-end members. */ private static final int FLAG_CHANGE_CONFIG_AT_END = 0x40; @IntDef(prefix = { "FLAG_" }, value = { FLAG_NONE, FLAG_SEAMLESS_ROTATION, Loading @@ -2947,7 +3050,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { FLAG_ABOVE_TRANSIENT_LAUNCH, FLAG_CHANGE_NO_ANIMATION, FLAG_CHANGE_YES_ANIMATION, FLAG_CHANGE_MOVED_TO_TOP FLAG_CHANGE_MOVED_TO_TOP, FLAG_CHANGE_CONFIG_AT_END }) @Retention(RetentionPolicy.SOURCE) @interface Flag {} Loading Loading @@ -3095,6 +3199,9 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { flags |= FLAG_IS_VOICE_INTERACTION; } flags |= record.mTransitionChangeFlags; if (record.isConfigurationDispatchPaused()) { flags |= FLAG_CONFIG_AT_END; } } final TaskFragment taskFragment = wc.asTaskFragment(); if (taskFragment != null && task == null) { Loading Loading @@ -3140,6 +3247,9 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if ((mFlags & FLAG_CHANGE_MOVED_TO_TOP) != 0) { flags |= FLAG_MOVED_TO_TOP; } if ((mFlags & FLAG_CHANGE_CONFIG_AT_END) != 0) { flags |= FLAG_CONFIG_AT_END; } return flags; } Loading
services/core/java/com/android/server/wm/WindowOrganizerController.java +15 −2 Original line number Diff line number Diff line Loading @@ -583,8 +583,21 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps(); final int hopSize = hops.size(); Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries = t.getChanges().entrySet().iterator(); Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries; if (transition != null) { // Mark any config-at-end containers before applying config changes so that // the config changes don't dispatch to client. entries = t.getChanges().entrySet().iterator(); while (entries.hasNext()) { final Map.Entry<IBinder, WindowContainerTransaction.Change> entry = entries.next(); if (!entry.getValue().getConfigAtTransitionEnd()) continue; final WindowContainer wc = WindowContainer.fromBinder(entry.getKey()); if (wc == null || !wc.isAttached()) continue; transition.setConfigAtEnd(wc); } } entries = t.getChanges().entrySet().iterator(); while (entries.hasNext()) { final Map.Entry<IBinder, WindowContainerTransaction.Change> entry = entries.next(); final WindowContainer wc = WindowContainer.fromBinder(entry.getKey()); Loading
services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +31 −0 Original line number Diff line number Diff line Loading @@ -33,6 +33,7 @@ import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.window.TransitionInfo.FLAG_CONFIG_AT_END; import static android.window.TransitionInfo.FLAG_FILLS_TASK; import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW; Loading Loading @@ -2539,6 +2540,36 @@ public class TransitionTests extends WindowTestsBase { } } @Test public void testConfigAtEnd() { final TransitionController controller = mDisplayContent.mTransitionController; Transition transit = createTestTransition(TRANSIT_CHANGE, controller); final TestTransitionPlayer player = registerTestTransitionPlayer(); final Task task = createTask(mDisplayContent); final Rect taskBounds = new Rect(0, 0, 200, 300); task.getConfiguration().windowConfiguration.setBounds(taskBounds); final ActivityRecord activity = createActivityRecord(task); activity.setVisibleRequested(true); activity.setVisible(true); controller.moveToCollecting(transit); transit.collect(task); transit.setConfigAtEnd(task); task.getRequestedOverrideConfiguration().windowConfiguration.setBounds( new Rect(10, 10, 200, 300)); task.onRequestedOverrideConfigurationChanged(task.getRequestedOverrideConfiguration()); controller.requestStartTransition(transit, task, null, null); player.start(); assertTrue(activity.isConfigurationDispatchPaused()); // config-at-end flag must propagate up to task if activity was promoted. assertTrue(player.mLastReady.getChange( task.mRemoteToken.toWindowContainerToken()).hasFlags(FLAG_CONFIG_AT_END)); player.finish(); assertFalse(activity.isConfigurationDispatchPaused()); } @Test public void testReadyTrackerBasics() { final TransitionController controller = new TestTransitionController( Loading