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<Transaction>), 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
This commit is contained in:
Robert Carr
2021-02-08 13:11:46 -08:00
committed by Tiger Huang
parent 9a2ae3ed4c
commit 932814a00e
8 changed files with 119 additions and 91 deletions

View File

@@ -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);

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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<WindowState> 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<Consumer<SurfaceControl.Transaction>> mPendingDrawHandlers
= new ArrayList<>();
private final List<Consumer<SurfaceControl.Transaction>> mReadyDrawHandlers
= new ArrayList<>();
private final Consumer<SurfaceControl.Transaction> 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<WindowState> 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<WindowState> 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<WindowState> 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<WindowState> 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<SurfaceControl.Transaction> 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;
}
}

View File

@@ -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());

View File

@@ -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);