From 932814a00eb19d51481f7d0fc34b1eba2967f241 Mon Sep 17 00:00:00 2001 From: Robert Carr Date: Mon, 8 Feb 2021 13:11:46 -0800 Subject: [PATCH] WindowState: Introduce applyWithNextDraw, port Seamless Rotation While BLASTSyncEngine is a good primitive for syncing changes to multiple window containers, it's complexity introduces constraints, namely that only one BLASTSyncEngine sync may be active at a time (for a given window container). As more and more parts of the WM wish to rely on BLASTSync, this can create a difficult coordination problem. In this CL we expose a per WindowState sync mechanism (applyWithNextDraw) which can compose with BLASTSyncEngine transactions, and can be used by multiple features simultaneously on a given WindowState. This system works by maintaining a list of "DrawHandlers" (Consumer), in each WindowState. At a high level, when adding a draw handler we request the client to redraw, and when we receive the Transaction containing the frame in finishDrawing we invoke the handler, giving it a chance to append to the transaction before it is applied or merged in to a higher level BLASTSyncEngine transaction. Next we port seamless rotation to this system. The concept is relatively easy, the setup proceeds as before: In the same transaction we rotate the display projection we create a SeamlessRotator to unrotate the apps original orientation buffer. Seamless rotation requires us to remove this transform when the app redraws, and this is trivially accomplished with the applyWithNextDraw primitive. Bug: 168505645 Bug: 181424834 Test: Existing tests pass Change-Id: Ie84d99e58b8d0efedbedab0ea48a441826e3a7bf --- .../com/android/server/wm/DisplayContent.java | 6 - .../android/server/wm/DisplayRotation.java | 20 +-- .../server/wm/InsetsSourceProvider.java | 28 +---- .../android/server/wm/SeamlessRotator.java | 18 --- .../server/wm/WindowManagerService.java | 15 +-- .../com/android/server/wm/WindowState.java | 119 ++++++++++++++++-- .../server/wm/InsetsSourceProviderTest.java | 2 +- .../android/server/wm/WindowStateTests.java | 2 +- 8 files changed, 119 insertions(+), 91 deletions(-) diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index eff4ea6536bd2..fd695935b2d5a 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -131,7 +131,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.H.REPORT_HARD_KEYBOARD_STATUS_CHANGE; import static com.android.server.wm.WindowManagerService.H.WINDOW_HIDE_TIMEOUT; import static com.android.server.wm.WindowManagerService.LAYOUT_REPEAT_THRESHOLD; -import static com.android.server.wm.WindowManagerService.SEAMLESS_ROTATION_TIMEOUT_DURATION; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_PLACING_SURFACES; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_REMOVING_FOCUS; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_ASSIGN_LAYERS; @@ -1811,11 +1810,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp w.mReportOrientationChanged = true; }, true /* traverseTopToBottom */); - if (rotateSeamlessly) { - mWmService.mH.sendNewMessageDelayed(WindowManagerService.H.SEAMLESS_ROTATION_TIMEOUT, - this, SEAMLESS_ROTATION_TIMEOUT_DURATION); - } - for (int i = mWmService.mRotationWatchers.size() - 1; i >= 0; i--) { final WindowManagerService.RotationWatcher rotationWatcher = mWmService.mRotationWatchers.get(i); diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index 63cb38a593491..5df1355f34602 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -567,7 +567,7 @@ public class DisplayRotation { } mDisplayContent.forAllWindows(w -> { if (w.mSeamlesslyRotated) { - w.finishSeamlessRotation(false /* timeout */); + w.cancelSeamlessRotation(); w.mSeamlesslyRotated = false; } }, true /* traverseTopToBottom */); @@ -670,24 +670,6 @@ public class DisplayRotation { } } - void onSeamlessRotationTimeout() { - final boolean[] isLayoutNeeded = { false }; - - mDisplayContent.forAllWindows(w -> { - if (!w.mSeamlesslyRotated) { - return; - } - isLayoutNeeded[0] = true; - w.setDisplayLayoutNeeded(); - w.finishSeamlessRotation(true /* timeout */); - markForSeamlessRotation(w, false /* seamlesslyRotated */); - }, true /* traverseTopToBottom */); - - if (isLayoutNeeded[0]) { - mService.mWindowPlacerLocked.performSurfacePlacement(); - } - } - /** * Returns the animation to run for a rotation transition based on the top fullscreen windows * {@link android.view.WindowManager.LayoutParams#rotationAnimation} and whether it is currently diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index c6c7fe083b167..45c4233b40aaa 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -93,7 +93,6 @@ class InsetsSourceProvider { private boolean mServerVisible; private boolean mSeamlessRotating; - private long mFinishSeamlessRotateFrameNumber = -1; private final boolean mControllable; @@ -342,15 +341,6 @@ class InsetsSourceProvider { mIsLeashReadyForDispatching = false; final SurfaceControl leash = mAdapter.mCapturedLeash; - final long frameNumber = mFinishSeamlessRotateFrameNumber; - mFinishSeamlessRotateFrameNumber = -1; - if (mWin.mHasSurface && leash != null) { - // We just finished the seamless rotation. We don't want to change the position or the - // window crop of the surface controls (including the leash) until the client finishes - // drawing the new frame of the new orientation. Although we cannot defer the reparent - // operation, it is fine, because reparent won't cause any visual effect. - deferTransactionUntil(t, leash, frameNumber); - } mControlTarget = target; updateVisibility(); mControl = new InsetsSourceControl(mSource.getType(), leash, surfacePosition); @@ -359,19 +349,14 @@ class InsetsSourceProvider { } void startSeamlessRotation() { - if (!mSeamlessRotating) { - mSeamlessRotating = true; - - // This will revoke the leash and clear the control target. - mWin.cancelAnimation(); - } + if (!mSeamlessRotating) { + mSeamlessRotating = true; + mWin.cancelAnimation(); + } } - void finishSeamlessRotation(boolean timeout) { - if (mSeamlessRotating) { - mSeamlessRotating = false; - mFinishSeamlessRotateFrameNumber = timeout ? -1 : mWin.getFrameNumber(); - } + void finishSeamlessRotation() { + mSeamlessRotating = false; } boolean updateClientVisibility(InsetsControlTarget caller) { @@ -529,7 +514,6 @@ class InsetsSourceProvider { proto.write(CLIENT_VISIBLE, mClientVisible); proto.write(SERVER_VISIBLE, mServerVisible); proto.write(SEAMLESS_ROTATING, mSeamlessRotating); - proto.write(FINISH_SEAMLESS_ROTATE_FRAME_NUMBER, mFinishSeamlessRotateFrameNumber); proto.write(CONTROLLABLE, mControllable); proto.end(token); } diff --git a/services/core/java/com/android/server/wm/SeamlessRotator.java b/services/core/java/com/android/server/wm/SeamlessRotator.java index 3d305e4d852d3..1e8b8a5bb576a 100644 --- a/services/core/java/com/android/server/wm/SeamlessRotator.java +++ b/services/core/java/com/android/server/wm/SeamlessRotator.java @@ -35,9 +35,6 @@ import java.io.StringWriter; * Helper class for seamless rotation. * * Works by transforming the {@link WindowState} back into the old display rotation. - * - * Uses {@link Transaction#deferTransactionUntil(SurfaceControl, IBinder, long)} instead of - * latching on the buffer size to allow for seamless 180 degree rotations. */ public class SeamlessRotator { @@ -103,22 +100,7 @@ public class SeamlessRotator { * Removing the transform and the result of the {@link WindowState} layout are both tied to the * {@link WindowState} next frame, such that they apply at the same time the client draws the * window in the new orientation. - * - * In the case of a rotation timeout, we want to remove the transform immediately and not defer - * it. */ - public void finish(WindowState win, boolean timeout) { - final Transaction t = win.getPendingTransaction(); - finish(t, win); - if (win.mWinAnimator.mSurfaceController != null && !timeout) { - t.deferTransactionUntil(win.mSurfaceControl, - win.getClientViewRootSurface(), win.getFrameNumber()); - t.deferTransactionUntil(win.mWinAnimator.mSurfaceController.mSurfaceControl, - win.getClientViewRootSurface(), win.getFrameNumber()); - } - } - - /** Removes the transform and restore to the original last position. */ void finish(Transaction t, WindowContainer win) { mTransform.reset(); t.setMatrix(win.mSurfaceControl, mTransform, mFloat9); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index cec03210efeb5..47e7c3c2c7d7e 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -367,9 +367,6 @@ public class WindowManagerService extends IWindowManager.Stub /** Amount of time (in milliseconds) to delay before declaring a window freeze timeout. */ static final int WINDOW_FREEZE_TIMEOUT_DURATION = 2000; - /** Amount of time (in milliseconds) to delay before declaring a seamless rotation timeout. */ - static final int SEAMLESS_ROTATION_TIMEOUT_DURATION = 2000; - /** Amount of time (in milliseconds) to delay before declaring a window replacement timeout. */ static final int WINDOW_REPLACEMENT_TIMEOUT_DURATION = 2000; @@ -2245,9 +2242,6 @@ public class WindowManagerService extends IWindowManager.Stub win.setFrameNumber(frameNumber); final DisplayContent dc = win.getDisplayContent(); - if (!dc.mWaitingForConfig) { - win.finishSeamlessRotation(false /* timeout */); - } if (win.mPendingPositionChanged != null) { win.mPendingPositionChanged.updateLeashPosition(frameNumber); @@ -2255,6 +2249,7 @@ public class WindowManagerService extends IWindowManager.Stub } if (mUseBLASTSync && win.useBLASTSync() && viewVisibility != View.GONE) { + win.prepareDrawHandlers(); result |= RELAYOUT_RES_BLAST_SYNC; } @@ -5080,7 +5075,6 @@ public class WindowManagerService extends IWindowManager.Stub public static final int UPDATE_ANIMATION_SCALE = 51; public static final int WINDOW_HIDE_TIMEOUT = 52; - public static final int SEAMLESS_ROTATION_TIMEOUT = 54; public static final int RESTORE_POINTER_ICON = 55; public static final int SET_HAS_OVERLAY_UI = 58; public static final int ANIMATION_FAILSAFE = 60; @@ -5363,13 +5357,6 @@ public class WindowManagerService extends IWindowManager.Stub } break; } - case SEAMLESS_ROTATION_TIMEOUT: { - final DisplayContent displayContent = (DisplayContent) msg.obj; - synchronized (mGlobalLock) { - displayContent.getDisplayRotation().onSeamlessRotationTimeout(); - } - break; - } case SET_HAS_OVERLAY_UI: { mAmInternal.setHasOverlayUi(msg.arg1, msg.arg2 == 1); break; diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 2a9e08e90e981..01be73196f9bf 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -263,6 +263,7 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import java.util.function.Consumer; import java.util.function.Predicate; /** A window in the window manager. */ @@ -751,6 +752,31 @@ class WindowState extends WindowContainer implements WindowManagerP private final WindowProcessController mWpcForDisplayAreaConfigChanges; + /** + * We split the draw handlers in to a "pending" and "ready" list, in order to solve + * sequencing problems. Think of it this way, let's say I update a windows orientation + * (in configuration), and then I call applyWithNextDraw. What I'm hoping for is to + * apply with the draw that contains the orientation change. However, since the client + * can call finishDrawing at any time, it could be about to call a previous call to + * finishDrawing (or maybe its already called it, we just haven't handled it). Since this + * frame was already completed it had no time to include the orientation change we made. + * To solve this problem we accumulate draw handlers in mPendingDrawHandlers, and then force + * the client to call relayout. Only the frame post relayout will contain the configuration + * change since the window has to relayout), and so in relayout we drain mPendingDrawHandlers + * into mReadyDrawHandlers. Finally once we get to finishDrawing we know everything in + * mReadyDrawHandlers corresponds to state which was observed by the client and we can + * invoke the consumers. + */ + private final List> mPendingDrawHandlers + = new ArrayList<>(); + private final List> mReadyDrawHandlers + = new ArrayList<>(); + + private final Consumer mSeamlessRotationFinishedConsumer = t -> { + finishSeamlessRotation(t); + updateSurfacePosition(t); + }; + /** * Returns the visibility of the given {@link InternalInsetsType type} requested by the client. * @@ -834,19 +860,27 @@ class WindowState extends WindowContainer implements WindowManagerP mPendingSeamlessRotate.unrotate(transaction, this); getDisplayContent().getDisplayRotation().markForSeamlessRotation(this, true /* seamlesslyRotated */); + applyWithNextDraw(mSeamlessRotationFinishedConsumer); } } - void finishSeamlessRotation(boolean timeout) { - if (mPendingSeamlessRotate != null) { - mPendingSeamlessRotate.finish(this, timeout); - mFinishSeamlessRotateFrameNumber = getFrameNumber(); - mPendingSeamlessRotate = null; - getDisplayContent().getDisplayRotation().markForSeamlessRotation(this, - false /* seamlesslyRotated */); - if (mControllableInsetProvider != null) { - mControllableInsetProvider.finishSeamlessRotation(timeout); - } + void cancelSeamlessRotation() { + finishSeamlessRotation(getPendingTransaction()); + } + + void finishSeamlessRotation(SurfaceControl.Transaction t) { + if (mPendingSeamlessRotate == null) { + return; + } + + mPendingSeamlessRotate.finish(t, this); + mFinishSeamlessRotateFrameNumber = getFrameNumber(); + mPendingSeamlessRotate = null; + + getDisplayContent().getDisplayRotation().markForSeamlessRotation(this, + false /* seamlesslyRotated */); + if (mControllableInsetProvider != null) { + mControllableInsetProvider.finishSeamlessRotation(); } } @@ -5762,6 +5796,8 @@ class WindowState extends WindowContainer implements WindowManagerP Slog.i(TAG, "finishDrawing of relaunch: " + this + " " + duration + "ms"); mActivityRecord.mRelaunchStartTime = 0; } + + executeDrawHandlers(postDrawTransaction); if (!onSyncFinishedDrawing()) { return mWinAnimator.finishDrawingLocked(postDrawTransaction); } @@ -5776,6 +5812,7 @@ class WindowState extends WindowContainer implements WindowManagerP } void immediatelyNotifyBlastSync() { + prepareDrawHandlers(); finishDrawing(null); mWmService.mH.removeMessages(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this); if (!useBLASTSync()) return; @@ -5855,4 +5892,66 @@ class WindowState extends WindowContainer implements WindowManagerP outSize.inset(-attrs.surfaceInsets.left, -attrs.surfaceInsets.top, -attrs.surfaceInsets.right, -attrs.surfaceInsets.bottom); } + + /** + * This method is used to control whether we return the BLAST_SYNC flag + * from relayoutWindow calls on this window (triggering the client to redirect + * it's next draw in to a transaction). If we have pending draw handlers, we are + * looking for the client to sync. + * + * See {@link WindowState#mPendingDrawHandlers} + */ + @Override + boolean useBLASTSync() { + return super.useBLASTSync() || (mPendingDrawHandlers.size() != 0); + } + + /** + * Apply the transaction with the next window redraw. A full relayout/finishDrawing + * cycle must occur before completion. This means if you call the function while + * "in relayout", the results may be undefined but at all other times the function + * should sort of transparently work like this: + * 1. Make changes to WM hierarchy (say change app configuration) + * 2. Call apply with next draw. + * 3. After finishDrawing, our consumer will be passed the Transaction + * containing the buffer, and we can merge in additional operations. + * See {@link WindowState#mPendingDrawHandlers} + */ + void applyWithNextDraw(Consumer consumer) { + mPendingDrawHandlers.add(consumer); + requestRedrawForSync(); + + mWmService.mH.sendNewMessageDelayed(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this, + BLAST_TIMEOUT_DURATION); + } + + /** + * Called from relayout, to indicate the next "finishDrawing" will contain + * all changes applied by the time mPendingDrawHandlers was populated. + * + * See {@link WindowState#mPendingDrawHandlers} + */ + void prepareDrawHandlers() { + mReadyDrawHandlers.addAll(mPendingDrawHandlers); + mPendingDrawHandlers.clear(); + } + + /** + * Drain the draw handlers, called from finishDrawing() + * See {@link WindowState#mPendingDrawHandlers} + */ + boolean executeDrawHandlers(SurfaceControl.Transaction t) { + if (t == null) t = mTmpTransaction; + boolean hadHandlers = false; + for (int i = 0; i < mReadyDrawHandlers.size(); i++) { + mReadyDrawHandlers.get(i).accept(t); + hadHandlers = true; + } + mReadyDrawHandlers.clear(); + mWmService.mH.removeMessages(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this); + + t.apply(); + + return hadHandlers; + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java index 983063125ce94..c483ae9fa4c53 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java @@ -141,7 +141,7 @@ public class InsetsSourceProviderTest extends WindowTestsBase { assertNull(mProvider.getControlTarget()); // We can have the control and the control target after seamless rotation. - mProvider.finishSeamlessRotation(false /* timeout */); + mProvider.finishSeamlessRotation(); mProvider.updateControlForTarget(target, false /* force */); assertNotNull(mProvider.getControl(target)); assertNotNull(mProvider.getControlTarget()); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index 51aec65f72856..5b5b1da327bd9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -521,7 +521,7 @@ public class WindowStateTests extends WindowTestsBase { matrix.mapPoints(curSurfacePos); verify(t).setPosition(eq(app.mSurfaceControl), eq(curSurfacePos[0]), eq(curSurfacePos[1])); - app.finishSeamlessRotation(false /* timeout */); + app.finishSeamlessRotation(t); assertFalse(app.mSeamlesslyRotated); assertNull(app.mPendingSeamlessRotate);