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:
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user