From 123ca798581b5987f0f8048b234f8aec444847b6 Mon Sep 17 00:00:00 2001 From: Evan Rosky Date: Tue, 13 Oct 2020 20:40:23 -0700 Subject: [PATCH] Convert BLASTSyncEngine to a polling mechanism SyncEngine's requirements are getting more demanding as it is used in transitions as-well as more-complicated interractions in Shell. In order to meet the requirements, this converts BLASTSyncEngine to work mostly by polling during surface-placement. The previous set-up was already gated on surface-placement, so there shouldn't be any additional latency. There is now only one SyncEngine (instead of many) and it manages SyncGroups which must not overlap. Whenever a container is added to a sync group, the SyncEngine will monitor the subtree rooted at that container. When a child receives finishDrawing: instead of immediately reporting to the engine, it simply remembers that it is now READY for sync. On each surface-placement pass, SyncEngine will check the subtrees to see if everything is finished syncing. This enabled the following: - Now handles visibility changes and new children (since it is always checking the full subtree). This is needed to deal with opening transitions. - Doesn't wait for occluded siblings. This is needed to deal with starting windows. - Handles parenting changes: immediately applies sync transactions on subtrees exiting the sync-group, requests updates/waits on subtrees entering, and re-requests updates for subtrees moving within the group. - Includes removals in sync transaction Bug: 161710818 Test: SyncEngineTests Change-Id: Ic3fdc091d5656b3eaea45bce449e098ee9d2aabb --- data/etc/services.core.protolog.json | 112 +++-- .../com/android/server/wm/ActivityRecord.java | 13 + .../android/server/wm/BLASTSyncEngine.java | 166 ++++---- .../server/wm/RootWindowContainer.java | 1 + .../com/android/server/wm/Transition.java | 28 +- .../server/wm/TransitionController.java | 1 - .../android/server/wm/WindowContainer.java | 219 +++++++--- .../server/wm/WindowManagerService.java | 4 + .../server/wm/WindowOrganizerController.java | 19 +- .../com/android/server/wm/WindowState.java | 90 +--- .../android/server/wm/SyncEngineTests.java | 390 ++++++++++++++++++ .../server/wm/WindowOrganizerTests.java | 163 ++------ .../android/server/wm/WindowStateTests.java | 4 +- 13 files changed, 773 insertions(+), 437 deletions(-) create mode 100644 services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 31dae22df4546..9ad44454a5dfe 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -121,6 +121,12 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "-1973119651": { + "message": "SyncGroup %d: Adding to group: %s", + "level": "VERBOSE", + "group": "WM_DEBUG_SYNC_ENGINE", + "at": "com\/android\/server\/wm\/BLASTSyncEngine.java" + }, "-1963461591": { "message": "Removing %s from %s", "level": "VERBOSE", @@ -157,14 +163,20 @@ "group": "WM_DEBUG_STARTING_WINDOW", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "-1918702467": { + "message": "onSyncFinishedDrawing %s", + "level": "VERBOSE", + "group": "WM_DEBUG_SYNC_ENGINE", + "at": "com\/android\/server\/wm\/WindowContainer.java" + }, "-1915280162": { "message": "Attempted to add wallpaper window with bad token %s. Aborting.", "level": "WARN", "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, - "-1910833551": { - "message": "SyncSet{%x:%d} Start for %s", + "-1905191109": { + "message": "SyncGroup %d: Finished!", "level": "VERBOSE", "group": "WM_DEBUG_SYNC_ENGINE", "at": "com\/android\/server\/wm\/BLASTSyncEngine.java" @@ -607,12 +619,6 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, - "-1387080937": { - "message": "SyncSet{%x:%d} Child ready, now ready=%b and waiting on %d transactions", - "level": "VERBOSE", - "group": "WM_DEBUG_SYNC_ENGINE", - "at": "com\/android\/server\/wm\/BLASTSyncEngine.java" - }, "-1376035390": { "message": "No task found", "level": "DEBUG", @@ -643,12 +649,6 @@ "group": "WM_DEBUG_BOOT", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, - "-1340783230": { - "message": "SyncSet{%x:%d} Added %s. now waiting on %d transactions", - "level": "VERBOSE", - "group": "WM_DEBUG_SYNC_ENGINE", - "at": "com\/android\/server\/wm\/BLASTSyncEngine.java" - }, "-1340540100": { "message": "Creating SnapshotStartingData", "level": "VERBOSE", @@ -1177,12 +1177,6 @@ "group": "WM_DEBUG_ORIENTATION", "at": "com\/android\/server\/wm\/DragState.java" }, - "-678300709": { - "message": "SyncSet{%x:%d} Trying to add %s", - "level": "VERBOSE", - "group": "WM_DEBUG_SYNC_ENGINE", - "at": "com\/android\/server\/wm\/BLASTSyncEngine.java" - }, "-677449371": { "message": "moveTaskToRootTask: moving task=%d to rootTaskId=%d toTop=%b", "level": "DEBUG", @@ -1405,12 +1399,6 @@ "group": "WM_DEBUG_RECENTS_ANIMATIONS", "at": "com\/android\/server\/wm\/RecentsAnimationController.java" }, - "-444624452": { - "message": "REPARENT from: %s to: %s", - "level": "INFO", - "group": "WM_SHOW_TRANSACTIONS", - "at": "com\/android\/server\/wm\/WindowSurfaceController.java" - }, "-443173857": { "message": "Moving pending starting from %s to %s", "level": "VERBOSE", @@ -1519,12 +1507,6 @@ "group": "WM_DEBUG_STATES", "at": "com\/android\/server\/wm\/Task.java" }, - "-324085783": { - "message": "SURFACE CROP %s: %s", - "level": "INFO", - "group": "WM_SHOW_TRANSACTIONS", - "at": "com\/android\/server\/wm\/WindowSurfaceController.java" - }, "-322743468": { "message": "setInputMethodInputTarget %s", "level": "INFO", @@ -1603,6 +1585,12 @@ "group": "WM_DEBUG_STATES", "at": "com\/android\/server\/wm\/Task.java" }, + "-230587670": { + "message": "SyncGroup %d: Unfinished container: %s", + "level": "VERBOSE", + "group": "WM_DEBUG_SYNC_ENGINE", + "at": "com\/android\/server\/wm\/BLASTSyncEngine.java" + }, "-198463978": { "message": "updateRotationUnchecked: alwaysSendConfiguration=%b forceRelayout=%b", "level": "VERBOSE", @@ -1711,12 +1699,6 @@ "group": "WM_DEBUG_APP_TRANSITIONS_ANIM", "at": "com\/android\/server\/wm\/WindowContainer.java" }, - "-29233992": { - "message": "SURFACE CLEAR CROP: %s", - "level": "INFO", - "group": "WM_SHOW_TRANSACTIONS", - "at": "com\/android\/server\/wm\/WindowSurfaceController.java" - }, "-21399771": { "message": "activity %s already destroying, skipping request with reason:%s", "level": "VERBOSE", @@ -2107,6 +2089,12 @@ "group": "WM_DEBUG_CONFIGURATION", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "371173718": { + "message": "finishSync cancel=%b for %s", + "level": "VERBOSE", + "group": "WM_DEBUG_SYNC_ENGINE", + "at": "com\/android\/server\/wm\/WindowContainer.java" + }, "371641947": { "message": "Window Manager Crash %s", "level": "WTF", @@ -2251,6 +2239,12 @@ "group": "WM_DEBUG_BOOT", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "550717438": { + "message": "SyncGroup %d: Started for listener: %s", + "level": "VERBOSE", + "group": "WM_DEBUG_SYNC_ENGINE", + "at": "com\/android\/server\/wm\/BLASTSyncEngine.java" + }, "556758086": { "message": "Applying new update lock state '%s' for %s", "level": "DEBUG", @@ -2287,12 +2281,6 @@ "group": "WM_DEBUG_IME", "at": "com\/android\/server\/wm\/ImeInsetsSourceProvider.java" }, - "590184240": { - "message": "- NOT adding to sync: visible=%b hasListener=%b", - "level": "VERBOSE", - "group": "WM_DEBUG_SYNC_ENGINE", - "at": "com\/android\/server\/wm\/WindowContainer.java" - }, "594260577": { "message": "createWallpaperAnimations()", "level": "DEBUG", @@ -2617,6 +2605,18 @@ "group": "WM_DEBUG_LOCKTASK", "at": "com\/android\/server\/wm\/LockTaskController.java" }, + "959486822": { + "message": "setSyncGroup #%d on %s", + "level": "VERBOSE", + "group": "WM_DEBUG_SYNC_ENGINE", + "at": "com\/android\/server\/wm\/WindowContainer.java" + }, + "966569777": { + "message": "SyncGroup %d: onSurfacePlacement checking %s", + "level": "VERBOSE", + "group": "WM_DEBUG_SYNC_ENGINE", + "at": "com\/android\/server\/wm\/BLASTSyncEngine.java" + }, "969323241": { "message": "Sending new config to %s, config: %s", "level": "VERBOSE", @@ -2641,12 +2641,6 @@ "group": "WM_DEBUG_WINDOW_TRANSITIONS", "at": "com\/android\/server\/wm\/Transition.java" }, - "1000601037": { - "message": "SyncSet{%x:%d} Set ready", - "level": "VERBOSE", - "group": "WM_DEBUG_SYNC_ENGINE", - "at": "com\/android\/server\/wm\/BLASTSyncEngine.java" - }, "1001509841": { "message": "Auto-PIP allowed, entering PIP mode directly: %s", "level": "DEBUG", @@ -2809,12 +2803,6 @@ "group": "WM_DEBUG_FOCUS", "at": "com\/android\/server\/wm\/WindowToken.java" }, - "1220075598": { - "message": "SURFACE SIZE %dx%d: %s", - "level": "INFO", - "group": "WM_SHOW_TRANSACTIONS", - "at": "com\/android\/server\/wm\/WindowSurfaceController.java" - }, "1224184681": { "message": "No longer Stopped: %s", "level": "VERBOSE", @@ -3181,6 +3169,12 @@ "group": "WM_DEBUG_CONFIGURATION", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "1689989893": { + "message": "SyncGroup %d: Set ready", + "level": "VERBOSE", + "group": "WM_DEBUG_SYNC_ENGINE", + "at": "com\/android\/server\/wm\/BLASTSyncEngine.java" + }, "1696210756": { "message": "Launch on display check: allow launch on public display", "level": "DEBUG", @@ -3409,12 +3403,6 @@ "group": "WM_DEBUG_CONFIGURATION", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, - "2001924866": { - "message": "SyncSet{%x:%d} Finished. Reporting %d containers to %s", - "level": "VERBOSE", - "group": "WM_DEBUG_SYNC_ENGINE", - "at": "com\/android\/server\/wm\/BLASTSyncEngine.java" - }, "2016061474": { "message": "Prepare app transition: transit=%s %s alwaysKeepCurrent=%b displayId=%d Callers=%s", "level": "VERBOSE", diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 746bc0eaff3c7..8ceb55791bac3 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -7877,4 +7877,17 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A pictureInPictureArgs.copyOnlySet(p); getTask().getRootTask().onPictureInPictureParamsChanged(); } + + @Override + boolean isSyncFinished() { + if (!super.isSyncFinished()) return false; + if (!isVisibleRequested()) return true; + // If visibleRequested, wait for at-least one visible child. + for (int i = mChildren.size() - 1; i >= 0; --i) { + if (mChildren.get(i).isVisibleRequested()) { + return true; + } + } + return false; + } } diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java index 958a7a8f07f86..301783c155e1f 100644 --- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java +++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java @@ -18,13 +18,13 @@ package com.android.server.wm; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SYNC_ENGINE; -import android.util.ArrayMap; +import android.annotation.NonNull; import android.util.ArraySet; +import android.util.SparseArray; +import android.view.SurfaceControl; import com.android.internal.protolog.common.ProtoLog; -import java.util.Set; - /** * Utility class for collecting WindowContainers that will merge transactions. * For example to use to synchronously resize all the children of a window container @@ -45,94 +45,122 @@ import java.util.Set; * 5. If there were no sub windows anywhere in the hierarchy to wait on, then * transactionReady is immediately invoked, otherwise all the windows are poked * to redraw and to deliver a buffer to {@link WindowState#finishDrawing}. - * Once all this drawing is complete the WindowContainer that's ready will be added to the - * set of ready WindowContainers. When the final onTransactionReady is called, it will merge - * the transactions of the all the WindowContainers and will be delivered to the - * TransactionReadyListener + * Once all this drawing is complete, all the transactions will be merged and delivered + * to TransactionReadyListener. + * + * This works primarily by setting-up state and then watching/waiting for the registered subtrees + * to enter into a "finished" state (either by receiving drawn content or by disappearing). This + * checks the subtrees during surface-placement. */ class BLASTSyncEngine { private static final String TAG = "BLASTSyncEngine"; interface TransactionReadyListener { - void onTransactionReady(int mSyncId, Set windowContainersReady); - }; + void onTransactionReady(int mSyncId, SurfaceControl.Transaction transaction); + } - // Holds state associated with a single synchronous set of operations. - class SyncState implements TransactionReadyListener { - int mSyncId; - int mRemainingTransactions; - TransactionReadyListener mListener; + /** + * Holds state associated with a single synchronous set of operations. + */ + class SyncGroup { + final int mSyncId; + final TransactionReadyListener mListener; boolean mReady = false; - Set mWindowContainersReady = new ArraySet<>(); + final ArraySet mRootMembers = new ArraySet<>(); + private SurfaceControl.Transaction mOrphanTransaction = null; - private void tryFinish() { - if (mRemainingTransactions == 0 && mReady) { - ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncSet{%x:%d} Finished. Reporting %d " - + "containers to %s", BLASTSyncEngine.this.hashCode(), mSyncId, - mWindowContainersReady.size(), mListener); - mListener.onTransactionReady(mSyncId, mWindowContainersReady); - mPendingSyncs.remove(mSyncId); - } - } - - public void onTransactionReady(int syncId, Set windowContainersReady) { - mRemainingTransactions--; - ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncSet{%x:%d} Child ready, now ready=%b" - + " and waiting on %d transactions", BLASTSyncEngine.this.hashCode(), mSyncId, - mReady, mRemainingTransactions); - mWindowContainersReady.addAll(windowContainersReady); - tryFinish(); - } - - void setReady() { - ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncSet{%x:%d} Set ready", - BLASTSyncEngine.this.hashCode(), mSyncId); - mReady = true; - tryFinish(); - } - - boolean addToSync(WindowContainer wc) { - ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncSet{%x:%d} Trying to add %s", - BLASTSyncEngine.this.hashCode(), mSyncId, wc); - if (wc.prepareForSync(this, mSyncId)) { - mRemainingTransactions++; - ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncSet{%x:%d} Added %s. now waiting " - + "on %d transactions", BLASTSyncEngine.this.hashCode(), mSyncId, wc, - mRemainingTransactions); - return true; - } - return false; - } - - SyncState(TransactionReadyListener l, int id) { - mListener = l; + private SyncGroup(TransactionReadyListener listener, int id) { mSyncId = id; - mRemainingTransactions = 0; + mListener = listener; } - }; + /** + * Gets a transaction to dump orphaned operations into. Orphaned operations are operations + * that were on the mSyncTransactions of "root" subtrees which have been removed during the + * sync period. + */ + @NonNull + SurfaceControl.Transaction getOrphanTransaction() { + if (mOrphanTransaction == null) { + // Lazy since this isn't common + mOrphanTransaction = mWm.mTransactionFactory.get(); + } + return mOrphanTransaction; + } + + private void onSurfacePlacement() { + if (!mReady) return; + ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: onSurfacePlacement checking %s", + mSyncId, mRootMembers); + for (int i = mRootMembers.size() - 1; i >= 0; --i) { + final WindowContainer wc = mRootMembers.valueAt(i); + if (!wc.isSyncFinished()) { + ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Unfinished container: %s", + mSyncId, wc); + return; + } + } + ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Finished!", mSyncId); + SurfaceControl.Transaction merged = mWm.mTransactionFactory.get(); + if (mOrphanTransaction != null) { + merged.merge(mOrphanTransaction); + } + for (WindowContainer wc : mRootMembers) { + wc.finishSync(merged, false /* cancel */); + } + mListener.onTransactionReady(mSyncId, merged); + mActiveSyncs.remove(mSyncId); + } + + private void setReady() { + ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Set ready", mSyncId); + mReady = true; + mWm.mWindowPlacerLocked.requestTraversal(); + } + + private void addToSync(WindowContainer wc) { + if (!mRootMembers.add(wc)) { + return; + } + ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Adding to group: %s", mSyncId, wc); + wc.setSyncGroup(this); + wc.prepareSync(); + mWm.mWindowPlacerLocked.requestTraversal(); + } + + void onCancelSync(WindowContainer wc) { + mRootMembers.remove(wc); + } + } + + private final WindowManagerService mWm; private int mNextSyncId = 0; + private final SparseArray mActiveSyncs = new SparseArray<>(); - private final ArrayMap mPendingSyncs = new ArrayMap<>(); - - BLASTSyncEngine() { + BLASTSyncEngine(WindowManagerService wms) { + mWm = wms; } int startSyncSet(TransactionReadyListener listener) { final int id = mNextSyncId++; - final SyncState s = new SyncState(listener, id); - mPendingSyncs.put(id, s); - ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncSet{%x:%d} Start for %s", hashCode(), id, listener); + final SyncGroup s = new SyncGroup(listener, id); + mActiveSyncs.put(id, s); + ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Started for listener: %s", id, listener); return id; } - boolean addToSyncSet(int id, WindowContainer wc) { - final SyncState st = mPendingSyncs.get(id); - return st.addToSync(wc); + void addToSyncSet(int id, WindowContainer wc) { + mActiveSyncs.get(id).addToSync(wc); } void setReady(int id) { - final SyncState st = mPendingSyncs.get(id); - st.setReady(); + mActiveSyncs.get(id).setReady(); + } + + void onSurfacePlacement() { + // backwards since each state can remove itself if finished + for (int i = mActiveSyncs.size() - 1; i >= 0; --i) { + mActiveSyncs.valueAt(i).onSurfacePlacement(); + } } } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 7e76e7553a3b0..904832b01d234 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -864,6 +864,7 @@ class RootWindowContainer extends WindowContainer mWmService.openSurfaceTransaction(); try { applySurfaceChangesTransaction(); + mWmService.mSyncEngine.onSurfacePlacement(); } catch (RuntimeException e) { Slog.wtf(TAG, "Unhandled exception in Window Manager", e); } finally { diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index fc67cd22ee69a..ac86698c630fe 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -17,6 +17,7 @@ package com.android.server.wm; +import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE; @@ -31,7 +32,6 @@ import android.os.RemoteException; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; -import android.view.Display; import android.view.SurfaceControl; import android.view.WindowManager; import android.view.animation.Animation; @@ -43,7 +43,6 @@ import com.android.internal.protolog.common.ProtoLog; import java.util.ArrayList; import java.util.Map; -import java.util.Set; /** * Represents a logical transition. @@ -70,6 +69,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe private int mSyncId; private @WindowManager.TransitionFlags int mFlags; private final TransitionController mController; + private final BLASTSyncEngine mSyncEngine; final ArrayMap mParticipants = new ArrayMap<>(); private int mState = STATE_COLLECTING; private boolean mReadyCalled = false; @@ -79,7 +79,8 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe mType = type; mFlags = flags; mController = controller; - mSyncId = mController.mSyncEngine.startSyncSet(this); + mSyncEngine = mController.mAtm.mWindowManager.mSyncEngine; + mSyncId = mSyncEngine.startSyncSet(this); } /** @@ -104,10 +105,8 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe if (mSyncId < 0) return; ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Collecting in transition %d: %s", mSyncId, wc); - // Add to sync set before checking contains because it may not have added it at other - // times (eg. if wc was previously invisible). - mController.mSyncEngine.addToSyncSet(mSyncId, wc); if (mParticipants.containsKey(wc)) return; + mSyncEngine.addToSyncSet(mSyncId, wc); mParticipants.put(wc, new ChangeInfo()); } @@ -125,8 +124,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe } ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Finish collecting in transition %d", mSyncId); - mController.mSyncEngine.setReady(mSyncId); - mController.mAtm.mWindowManager.mWindowPlacerLocked.requestTraversal(); + mSyncEngine.setReady(mSyncId); } /** The transition has finished animating and is ready to finalize WM state */ @@ -146,7 +144,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe } @Override - public void onTransactionReady(int syncId, Set windowContainersReady) { + public void onTransactionReady(int syncId, SurfaceControl.Transaction transaction) { if (syncId != mSyncId) { Slog.e(TAG, "Unexpected Sync ID " + syncId + ". Expected " + mSyncId); return; @@ -155,10 +153,8 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe mController.moveToPlaying(this); final TransitionInfo info = calculateTransitionInfo(mType, mParticipants); - SurfaceControl.Transaction mergedTransaction = new SurfaceControl.Transaction(); - int displayId = Display.DEFAULT_DISPLAY; - for (WindowContainer container : windowContainersReady) { - container.mergeBlastSyncTransaction(mergedTransaction); + int displayId = DEFAULT_DISPLAY; + for (WindowContainer container : mParticipants.keySet()) { displayId = container.mDisplayContent.getDisplayId(); } @@ -168,14 +164,14 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe try { ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Calling onTransitionReady: %s", info); - mController.getTransitionPlayer().onTransitionReady(this, info, mergedTransaction); + mController.getTransitionPlayer().onTransitionReady(this, info, transaction); } catch (RemoteException e) { // If there's an exception when trying to send the mergedTransaction to the // client, we should immediately apply it here so the transactions aren't lost. - mergedTransaction.apply(); + transaction.apply(); } } else { - mergedTransaction.apply(); + transaction.apply(); } mSyncId = -1; } diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index d102c19bfff92..7cdc177e1b9bb 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -51,7 +51,6 @@ class TransitionController { Arrays.sort(SUPPORTED_LEGACY_TRANSIT_TYPES); } - final BLASTSyncEngine mSyncEngine = new BLASTSyncEngine(); private ITransitionPlayer mTransitionPlayer; private final IBinder.DeathRecipient mTransitionPlayerDeath = () -> mTransitionPlayer = null; final ActivityTaskManagerService mAtm; diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 0edaa1d821df4..da3a92856fe58 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -58,6 +58,7 @@ import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM; import android.annotation.CallSuper; import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.WindowConfiguration; import android.content.pm.ActivityInfo; @@ -96,7 +97,6 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Comparator; import java.util.LinkedList; -import java.util.Set; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; @@ -109,8 +109,7 @@ import java.util.function.Predicate; * changes are made to this class. */ class WindowContainer extends ConfigurationContainer - implements Comparable, Animatable, SurfaceFreezer.Freezable, - BLASTSyncEngine.TransactionReadyListener { + implements Comparable, Animatable, SurfaceFreezer.Freezable { private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowContainer" : TAG_WM; @@ -290,16 +289,35 @@ class WindowContainer extends ConfigurationContainer< */ RemoteToken mRemoteToken = null; - BLASTSyncEngine mBLASTSyncEngine = new BLASTSyncEngine(); - SurfaceControl.Transaction mBLASTSyncTransaction; - boolean mUsingBLASTSyncTransaction = false; - BLASTSyncEngine.TransactionReadyListener mWaitingListener; - int mWaitingSyncId; + /** This isn't participating in a sync. */ + public static final int SYNC_STATE_NONE = 0; + + /** This is currently waiting for itself to finish drawing. */ + public static final int SYNC_STATE_WAITING_FOR_DRAW = 1; + + /** This container is ready, but it might still have unfinished children. */ + public static final int SYNC_STATE_READY = 2; + + @IntDef(prefix = { "SYNC_STATE_" }, value = { + SYNC_STATE_NONE, + SYNC_STATE_WAITING_FOR_DRAW, + SYNC_STATE_READY, + }) + @interface SyncState {} + + /** + * If non-null, references the sync-group directly waiting on this container. Otherwise, this + * container is only being waited-on by its parents (if in a sync-group). This has implications + * on how this container is handled during parent changes. + */ + BLASTSyncEngine.SyncGroup mSyncGroup = null; + final SurfaceControl.Transaction mSyncTransaction; + @SyncState int mSyncState = SYNC_STATE_NONE; WindowContainer(WindowManagerService wms) { mWmService = wms; mPendingTransaction = wms.mTransactionFactory.get(); - mBLASTSyncTransaction = wms.mTransactionFactory.get(); + mSyncTransaction = wms.mTransactionFactory.get(); mSurfaceAnimator = new SurfaceAnimator(this, this::onAnimationFinished, wms); mSurfaceFreezer = new SurfaceFreezer(this, wms); } @@ -357,6 +375,7 @@ class WindowContainer extends ConfigurationContainer< // Send onParentChanged notification here is we disabled sending it in setParent for // reparenting case. onParentChanged(newParent, oldParent); + onSyncReparent(oldParent, newParent); } final protected void setParent(WindowContainer parent) { @@ -372,6 +391,7 @@ class WindowContainer extends ConfigurationContainer< onDisplayChanged(mParent.mDisplayContent); } onParentChanged(mParent, oldParent); + onSyncReparent(oldParent, mParent); } } @@ -612,6 +632,8 @@ class WindowContainer extends ConfigurationContainer< scheduleAnimation(); } + // This must happen after updating the surface so that sync transactions can be handled + // properly. if (mParent != null) { mParent.removeChild(this); } @@ -2247,8 +2269,8 @@ class WindowContainer extends ConfigurationContainer< * {@link #getPendingTransaction()} */ public Transaction getSyncTransaction() { - if (mUsingBLASTSyncTransaction) { - return mBLASTSyncTransaction; + if (mSyncState != SYNC_STATE_NONE) { + return mSyncTransaction; } return getPendingTransaction(); @@ -2895,69 +2917,140 @@ class WindowContainer extends ConfigurationContainer< } } - @Override - public void onTransactionReady(int mSyncId, Set windowContainersReady) { - if (mWaitingListener == null) { - return; - } - - windowContainersReady.add(this); - mWaitingListener.onTransactionReady(mWaitingSyncId, windowContainersReady); - - mWaitingListener = null; - mWaitingSyncId = -1; - } - /** - * Returns true if any of the children elected to participate in the Sync + * Call this when this container finishes drawing content. + * + * @return {@code true} if consumed (this container is part of a sync group). */ - boolean addChildrenToSyncSet(int localId) { - boolean willSync = false; - - for (int i = 0; i < mChildren.size(); i++) { - final WindowContainer child = mChildren.get(i); - willSync |= mBLASTSyncEngine.addToSyncSet(localId, child); - } - return willSync; - } - - boolean setPendingListener(BLASTSyncEngine.TransactionReadyListener waitingListener, - int waitingId) { - // If we are invisible, no need to sync, likewise if we are already engaged in a sync, - // we can't support overlapping syncs on a single container yet. - if (!isVisible() || mWaitingListener != null) { - ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "- NOT adding to sync: visible=%b " - + "hasListener=%b", isVisible(), mWaitingListener != null); - return false; - } - mUsingBLASTSyncTransaction = true; - - // Make sure to set these before we call setReady in case the sync was a no-op - mWaitingSyncId = waitingId; - mWaitingListener = waitingListener; + boolean onSyncFinishedDrawing() { + if (mSyncState == SYNC_STATE_NONE) return false; + mSyncState = SYNC_STATE_READY; + mWmService.mWindowPlacerLocked.requestTraversal(); + ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "onSyncFinishedDrawing %s", this); return true; } - boolean prepareForSync(BLASTSyncEngine.TransactionReadyListener waitingListener, - int waitingId) { - boolean willSync = setPendingListener(waitingListener, waitingId); - if (!willSync) { + void setSyncGroup(@NonNull BLASTSyncEngine.SyncGroup group) { + ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "setSyncGroup #%d on %s", group.mSyncId, this); + if (group != null) { + if (mSyncGroup != null && mSyncGroup != group) { + throw new IllegalStateException("Can't sync on 2 engines simultaneously"); + } + } + mSyncGroup = group; + } + + /** + * Prepares this container for participation in a sync-group. This includes preparing all its + * children. + * + * @return {@code true} if something changed (eg. this wasn't already in the sync group). + */ + boolean prepareSync() { + if (mSyncState != SYNC_STATE_NONE) { + // Already part of sync return false; } - - int localId = mBLASTSyncEngine.startSyncSet(this); - willSync |= addChildrenToSyncSet(localId); - mBLASTSyncEngine.setReady(localId); - - return willSync; + for (int i = getChildCount() - 1; i >= 0; --i) { + final WindowContainer child = getChildAt(i); + child.prepareSync(); + } + mSyncState = SYNC_STATE_READY; + return true; } boolean useBLASTSync() { - return mUsingBLASTSyncTransaction; + return mSyncState != SYNC_STATE_NONE; } - void mergeBlastSyncTransaction(Transaction t) { - t.merge(mBLASTSyncTransaction); - mUsingBLASTSyncTransaction = false; + /** + * Recursively finishes/cleans-up sync state of this subtree and collects all the sync + * transactions into `outMergedTransaction`. + * @param outMergedTransaction A transaction to merge all the recorded sync operations into. + * @param cancel If true, this is being finished because it is leaving the sync group rather + * than due to the sync group completing. + */ + void finishSync(Transaction outMergedTransaction, boolean cancel) { + if (mSyncState == SYNC_STATE_NONE) return; + ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "finishSync cancel=%b for %s", cancel, this); + outMergedTransaction.merge(mSyncTransaction); + for (int i = mChildren.size() - 1; i >= 0; --i) { + mChildren.get(i).finishSync(outMergedTransaction, cancel); + } + mSyncState = SYNC_STATE_NONE; + if (cancel && mSyncGroup != null) mSyncGroup.onCancelSync(this); + mSyncGroup = null; + } + + /** + * Checks if the subtree rooted at this container is finished syncing (everything is ready or + * not visible). NOTE, this is not const: it will cancel/prepare itself depending on its state + * in the hierarchy. + * + * @return {@code true} if this subtree is finished waiting for sync participants. + */ + boolean isSyncFinished() { + if (!isVisibleRequested()) { + return true; + } + if (mSyncState == SYNC_STATE_NONE) { + prepareSync(); + } + if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW) { + return false; + } + // READY + // Loop from top-down. + for (int i = mChildren.size() - 1; i >= 0; --i) { + final WindowContainer child = mChildren.get(i); + final boolean childFinished = child.isSyncFinished(); + if (childFinished && child.isVisibleRequested() && child.fillsParent()) { + // Any lower children will be covered-up, so we can consider this finished. + return true; + } + if (!childFinished) { + return false; + } + } + return true; + } + + /** + * Called during reparent to handle sync state when the hierarchy changes. + * If this is in a sync group and gets reparented out, it will cancel syncing. + * If this is not in a sync group and gets parented into one, it will prepare itself. + * If its moving around within a sync-group, it needs to restart its syncing since a + * hierarchy change implies a configuration change. + */ + private void onSyncReparent(WindowContainer oldParent, WindowContainer newParent) { + if (newParent == null || newParent.mSyncState == SYNC_STATE_NONE) { + if (mSyncState == SYNC_STATE_NONE) { + return; + } + if (newParent == null) { + // This is getting removed. + if (oldParent.mSyncState != SYNC_STATE_NONE) { + // In order to keep the transaction in sync, merge it into the parent. + finishSync(oldParent.mSyncTransaction, true /* cancel */); + } else if (mSyncGroup != null) { + // This is watched directly by the sync-group, so merge this transaction into + // into the sync-group so it isn't lost + finishSync(mSyncGroup.getOrphanTransaction(), true /* cancel */); + } else { + throw new IllegalStateException("This container is in sync mode without a sync" + + " group: " + this); + } + return; + } else if (mSyncGroup == null) { + // This is being reparented out of the sync-group. To prevent ordering issues on + // this container, immediately apply/cancel sync on it. + finishSync(getPendingTransaction(), true /* cancel */); + return; + } + // Otherwise this is the "root" of a synced subtree, so continue on to preparation. + } + // This container's situation has changed so we need to restart its sync. + mSyncState = SYNC_STATE_NONE; + prepareSync(); } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 4be118ec97fa7..93670c8529dad 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -669,6 +669,8 @@ public class WindowManagerService extends IWindowManager.Stub // Whether to enable BLASTSyncEngine Transaction passing. final boolean mUseBLASTSync = false; + final BLASTSyncEngine mSyncEngine; + int mDockedStackCreateMode = SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; Rect mDockedStackCreateBounds; @@ -1193,6 +1195,8 @@ public class WindowManagerService extends IWindowManager.Stub DeviceConfig.NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT, WM_USE_BLAST_ADAPTER_FLAG, false); + mSyncEngine = new BLASTSyncEngine(this); + mWindowPlacerLocked = new WindowSurfacePlacer(this); mTaskSnapshotController = new TaskSnapshotController(this); diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 5e07f5187c546..ae152d253d176 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -57,7 +57,6 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Set; /** * Server side implementation for the interface for organizing windows @@ -84,7 +83,6 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub private final ActivityTaskManagerService mService; private final WindowManagerGlobalLock mGlobalLock; - private final BLASTSyncEngine mBLASTSyncEngine = new BLASTSyncEngine(); private final HashMap mTransactionCallbacksByPendingSyncId = new HashMap(); @@ -505,7 +503,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub @VisibleForTesting int startSyncWithOrganizer(IWindowContainerTransactionCallback callback) { - int id = mBLASTSyncEngine.startSyncSet(this); + int id = mService.mWindowManager.mSyncEngine.startSyncSet(this); mTransactionCallbacksByPendingSyncId.put(id, callback); return id; } @@ -513,31 +511,26 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub @VisibleForTesting void setSyncReady(int id) { ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Set sync ready, syncId=%d", id); - mBLASTSyncEngine.setReady(id); + mService.mWindowManager.mSyncEngine.setReady(id); } @VisibleForTesting void addToSyncSet(int syncId, WindowContainer wc) { - mBLASTSyncEngine.addToSyncSet(syncId, wc); + mService.mWindowManager.mSyncEngine.addToSyncSet(syncId, wc); } @Override - public void onTransactionReady(int syncId, Set windowContainersReady) { + public void onTransactionReady(int syncId, SurfaceControl.Transaction t) { ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Transaction ready, syncId=%d", syncId); final IWindowContainerTransactionCallback callback = mTransactionCallbacksByPendingSyncId.get(syncId); - SurfaceControl.Transaction mergedTransaction = new SurfaceControl.Transaction(); - for (WindowContainer container : windowContainersReady) { - container.mergeBlastSyncTransaction(mergedTransaction); - } - try { - callback.onTransactionReady(syncId, mergedTransaction); + callback.onTransactionReady(syncId, t); } catch (RemoteException e) { // If there's an exception when trying to send the mergedTransaction to the client, we // should immediately apply it here so the transactions aren't lost. - mergedTransaction.apply(); + t.apply(); } mTransactionCallbacksByPendingSyncId.remove(syncId); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 25b48281db8e6..9f2c909c9bf1c 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -250,10 +250,8 @@ import com.android.server.wm.utils.WmDisplayCutout; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; -import java.util.Collections; import java.util.Comparator; import java.util.List; -import java.util.Set; import java.util.function.Predicate; /** A window in the window manager. */ @@ -656,15 +654,6 @@ class WindowState extends WindowContainer implements WindowManagerP private static final StringBuilder sTmpSB = new StringBuilder(); - /** - * Whether the next surfacePlacement call should notify that the blast sync is ready. - * This is set to true when {@link #finishDrawing(Transaction)} is called so - * {@link #onTransactionReady(int, Set)} is called after the next surfacePlacement. This allows - * Transactions to get flushed into the syncTransaction before notifying {@link BLASTSyncEngine} - * that this WindowState is ready. - */ - private boolean mNotifyBlastOnSurfacePlacement; - /** * Compares two window sub-layers and returns -1 if the first is lesser than the second in terms * of z-order and 1 otherwise. @@ -705,15 +694,6 @@ class WindowState extends WindowContainer implements WindowManagerP */ int mFrameRateSelectionPriority = RefreshRatePolicy.LAYER_PRIORITY_UNSET; - /** - * BLASTSyncEngine ID corresponding to a sync-set for all - * our children. We add our children to this set in Sync, - * but we save it and don't mark it as ready until finishDrawing - * this way we have a two way latch between all our children finishing - * and drawing ourselves. - */ - private int mLocalSyncId = -1; - static final int BLAST_TIMEOUT_DURATION = 5000; /* milliseconds */ private final WindowProcessController mWpcForDisplayConfigChanges; @@ -2227,7 +2207,6 @@ class WindowState extends WindowContainer implements WindowManagerP void removeIfPossible() { super.removeIfPossible(); removeIfPossible(false /*keepVisibleDeadWindow*/); - immediatelyNotifyBlastSync(); } private void removeIfPossible(boolean keepVisibleDeadWindow) { @@ -5329,7 +5308,6 @@ class WindowState extends WindowContainer implements WindowManagerP updateFrameRateSelectionPriorityIfNeeded(); mWinAnimator.prepareSurfaceLocked(SurfaceControl.getGlobalTransaction(), true); - notifyBlastSyncTransaction(); super.prepareSurfaces(); } @@ -5760,13 +5738,6 @@ class WindowState extends WindowContainer implements WindowManagerP void setViewVisibility(int viewVisibility) { mViewVisibility = viewVisibility; - // The viewVisibility is set to GONE with a client request to relayout. If this occurs and - // there's a blast sync transaction waiting, finishDrawing will never be called since the - // client will not render when visibility is GONE. Therefore, call finishDrawing here to - // prevent system server from blocking on a window that will not draw. - if (viewVisibility == View.GONE && mUsingBLASTSyncTransaction) { - immediatelyNotifyBlastSync(); - } } SurfaceControl getClientViewRootSurface() { @@ -5774,86 +5745,57 @@ class WindowState extends WindowContainer implements WindowManagerP } @Override - boolean prepareForSync(BLASTSyncEngine.TransactionReadyListener waitingListener, - int waitingId) { - // If the window is goneForLayout, relayout won't be called so we'd just wait forever. - if (isGoneForLayout()) { + boolean prepareSync() { + if (!super.prepareSync()) { return false; } - boolean willSync = setPendingListener(waitingListener, waitingId); - if (!willSync) { - return false; - } - requestRedrawForSync(); - - mLocalSyncId = mBLASTSyncEngine.startSyncSet(this); - addChildrenToSyncSet(mLocalSyncId); - // In the WindowContainer implementation we immediately mark ready // since a generic WindowContainer only needs to wait for its // children to finish and is immediately ready from its own // perspective but at the WindowState level we need to wait for ourselves - // to draw even if the children draw first our don't need to sync, so we omit - // the set ready call until later in finishDrawing() + // to draw even if the children draw first our don't need to sync, so we start + // in WAITING state rather than READY. + mSyncState = SYNC_STATE_WAITING_FOR_DRAW; + requestRedrawForSync(); + mWmService.mH.removeMessages(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this); mWmService.mH.sendNewMessageDelayed(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this, BLAST_TIMEOUT_DURATION); - return true; } boolean finishDrawing(SurfaceControl.Transaction postDrawTransaction) { - if (!mUsingBLASTSyncTransaction) { + if (!onSyncFinishedDrawing()) { return mWinAnimator.finishDrawingLocked(postDrawTransaction); } if (postDrawTransaction != null) { - mBLASTSyncTransaction.merge(postDrawTransaction); + mSyncTransaction.merge(postDrawTransaction); } - mNotifyBlastOnSurfacePlacement = true; mWinAnimator.finishDrawingLocked(null); // We always want to force a traversal after a finish draw for blast sync. return true; } - private void notifyBlastSyncTransaction() { + void immediatelyNotifyBlastSync() { + finishDrawing(null); mWmService.mH.removeMessages(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this); - - if (!mNotifyBlastOnSurfacePlacement || mWaitingListener == null) { - mNotifyBlastOnSurfacePlacement = false; - return; - } + if (!useBLASTSync()) return; final Task task = getTask(); if (task != null) { final SurfaceControl.Transaction t = task.getMainWindowSizeChangeTransaction(); if (t != null) { - mBLASTSyncTransaction.merge(t); + mSyncTransaction.merge(t); } task.setMainWindowSizeChangeTransaction(null); } - - // If localSyncId is >0 then we are syncing with children and will - // invoke transaction ready from our own #transactionReady callback - // we just need to signal our side of the sync (setReady). But if we - // have no sync operation at this level transactionReady will never - // be invoked and we need to invoke it ourself. - if (mLocalSyncId >= 0) { - mBLASTSyncEngine.setReady(mLocalSyncId); - return; - } - - mWaitingListener.onTransactionReady(mWaitingSyncId, Collections.singleton(this)); - - mWaitingSyncId = 0; - mWaitingListener = null; - mNotifyBlastOnSurfacePlacement = false; } - void immediatelyNotifyBlastSync() { - finishDrawing(null); - notifyBlastSyncTransaction(); + @Override + boolean fillsParent() { + return mAttrs.type == TYPE_APPLICATION_STARTING; } /** diff --git a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java new file mode 100644 index 0000000000000..7bac3e7b86792 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java @@ -0,0 +1,390 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.server.wm.WindowContainer.POSITION_BOTTOM; +import static com.android.server.wm.WindowContainer.POSITION_TOP; +import static com.android.server.wm.WindowContainer.SYNC_STATE_NONE; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.notNull; +import static org.mockito.Mockito.spy; + +import android.platform.test.annotations.Presubmit; +import android.view.SurfaceControl; + +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; + +/** + * Test class for {@link BLASTSyncEngine}. + * + * Build/Install/Run: + * atest WmTests:SyncEngineTests + */ +@SmallTest +@Presubmit +@RunWith(WindowTestRunner.class) +public class SyncEngineTests extends WindowTestsBase { + + @Before + public void setUp() { + spyOn(mWm.mWindowPlacerLocked); + } + + @Test + public void testTrivialSyncCallback() { + TestWindowContainer mockWC = new TestWindowContainer(mWm, false /* waiter */); + + BLASTSyncEngine bse = new BLASTSyncEngine(mWm); + + BLASTSyncEngine.TransactionReadyListener listener = mock( + BLASTSyncEngine.TransactionReadyListener.class); + + int id = bse.startSyncSet(listener); + bse.addToSyncSet(id, mockWC); + // Make sure a traversal is requested + verify(mWm.mWindowPlacerLocked, times(1)).requestTraversal(); + + bse.onSurfacePlacement(); + verify(listener, times(0)).onTransactionReady(anyInt(), any()); + + bse.setReady(id); + // Make sure a traversal is requested + verify(mWm.mWindowPlacerLocked, times(2)).requestTraversal(); + bse.onSurfacePlacement(); + verify(listener, times(1)).onTransactionReady(eq(id), notNull()); + + // make sure it was cleaned-up (no second callback) + bse.onSurfacePlacement(); + verify(listener, times(1)).onTransactionReady(anyInt(), any()); + } + + @Test + public void testWaitingSyncCallback() { + TestWindowContainer mockWC = new TestWindowContainer(mWm, true /* waiter */); + + BLASTSyncEngine bse = new BLASTSyncEngine(mWm); + + BLASTSyncEngine.TransactionReadyListener listener = mock( + BLASTSyncEngine.TransactionReadyListener.class); + + int id = bse.startSyncSet(listener); + bse.addToSyncSet(id, mockWC); + bse.setReady(id); + // Make sure traversals requested (one for add and another for setReady) + verify(mWm.mWindowPlacerLocked, times(2)).requestTraversal(); + bse.onSurfacePlacement(); + verify(listener, times(0)).onTransactionReady(anyInt(), any()); + + mockWC.onSyncFinishedDrawing(); + // Make sure a (third) traversal is requested. + verify(mWm.mWindowPlacerLocked, times(3)).requestTraversal(); + bse.onSurfacePlacement(); + verify(listener, times(1)).onTransactionReady(eq(id), notNull()); + } + + @Test + public void testInvisibleSyncCallback() { + TestWindowContainer mockWC = new TestWindowContainer(mWm, true /* waiter */); + + BLASTSyncEngine bse = new BLASTSyncEngine(mWm); + + BLASTSyncEngine.TransactionReadyListener listener = mock( + BLASTSyncEngine.TransactionReadyListener.class); + + int id = bse.startSyncSet(listener); + bse.addToSyncSet(id, mockWC); + bse.setReady(id); + // Make sure traversals requested (one for add and another for setReady) + verify(mWm.mWindowPlacerLocked, times(2)).requestTraversal(); + bse.onSurfacePlacement(); + verify(listener, times(0)).onTransactionReady(anyInt(), any()); + + // Finish sync if invisible. + mockWC.mVisibleRequested = false; + bse.onSurfacePlacement(); + verify(listener, times(1)).onTransactionReady(eq(id), notNull()); + assertEquals(SYNC_STATE_NONE, mockWC.mSyncState); + } + + @Test + public void testWaitForChildrenCallback() { + TestWindowContainer parentWC = new TestWindowContainer(mWm, true /* waiter */); + TestWindowContainer childWC = new TestWindowContainer(mWm, true /* waiter */); + TestWindowContainer childWC2 = new TestWindowContainer(mWm, true /* waiter */); + parentWC.addChild(childWC, POSITION_TOP); + parentWC.addChild(childWC2, POSITION_TOP); + + BLASTSyncEngine bse = new BLASTSyncEngine(mWm); + + BLASTSyncEngine.TransactionReadyListener listener = mock( + BLASTSyncEngine.TransactionReadyListener.class); + + int id = bse.startSyncSet(listener); + bse.addToSyncSet(id, parentWC); + bse.setReady(id); + bse.onSurfacePlacement(); + verify(listener, times(0)).onTransactionReady(anyInt(), any()); + + parentWC.onSyncFinishedDrawing(); + bse.onSurfacePlacement(); + verify(listener, times(0)).onTransactionReady(anyInt(), any()); + + childWC.onSyncFinishedDrawing(); + bse.onSurfacePlacement(); + verify(listener, times(0)).onTransactionReady(anyInt(), any()); + + childWC2.onSyncFinishedDrawing(); + bse.onSurfacePlacement(); + verify(listener, times(1)).onTransactionReady(eq(id), notNull()); + assertEquals(SYNC_STATE_NONE, parentWC.mSyncState); + assertEquals(SYNC_STATE_NONE, childWC.mSyncState); + assertEquals(SYNC_STATE_NONE, childWC2.mSyncState); + } + + @Test + public void testWaitForParentCallback() { + TestWindowContainer parentWC = new TestWindowContainer(mWm, true /* waiter */); + TestWindowContainer childWC = new TestWindowContainer(mWm, true /* waiter */); + parentWC.addChild(childWC, POSITION_TOP); + + BLASTSyncEngine bse = new BLASTSyncEngine(mWm); + + BLASTSyncEngine.TransactionReadyListener listener = mock( + BLASTSyncEngine.TransactionReadyListener.class); + + int id = bse.startSyncSet(listener); + bse.addToSyncSet(id, parentWC); + bse.setReady(id); + bse.onSurfacePlacement(); + verify(listener, times(0)).onTransactionReady(anyInt(), any()); + + childWC.onSyncFinishedDrawing(); + bse.onSurfacePlacement(); + verify(listener, times(0)).onTransactionReady(anyInt(), any()); + + parentWC.onSyncFinishedDrawing(); + bse.onSurfacePlacement(); + verify(listener, times(1)).onTransactionReady(eq(id), notNull()); + assertEquals(SYNC_STATE_NONE, parentWC.mSyncState); + assertEquals(SYNC_STATE_NONE, childWC.mSyncState); + } + + @Test + public void testFillsParent() { + TestWindowContainer parentWC = new TestWindowContainer(mWm, true /* waiter */); + TestWindowContainer topChildWC = new TestWindowContainer(mWm, true /* waiter */); + TestWindowContainer botChildWC = new TestWindowContainer(mWm, true /* waiter */); + topChildWC.mFillsParent = botChildWC.mFillsParent = true; + parentWC.addChild(topChildWC, POSITION_TOP); + parentWC.addChild(botChildWC, POSITION_BOTTOM); + + BLASTSyncEngine bse = new BLASTSyncEngine(mWm); + + BLASTSyncEngine.TransactionReadyListener listener = mock( + BLASTSyncEngine.TransactionReadyListener.class); + + int id = bse.startSyncSet(listener); + bse.addToSyncSet(id, parentWC); + bse.setReady(id); + bse.onSurfacePlacement(); + verify(listener, times(0)).onTransactionReady(anyInt(), any()); + + parentWC.onSyncFinishedDrawing(); + topChildWC.onSyncFinishedDrawing(); + // Even though bottom isn't finished, we should see callback because it is occluded by top. + assertFalse(botChildWC.isSyncFinished()); + bse.onSurfacePlacement(); + verify(listener, times(1)).onTransactionReady(eq(id), notNull()); + + assertEquals(SYNC_STATE_NONE, parentWC.mSyncState); + assertEquals(SYNC_STATE_NONE, botChildWC.mSyncState); + assertEquals(SYNC_STATE_NONE, topChildWC.mSyncState); + } + + @Test + public void testReparentOut() { + TestWindowContainer nonMemberParentWC = new TestWindowContainer(mWm, true /* waiter */); + TestWindowContainer parentWC = new TestWindowContainer(mWm, true /* waiter */); + TestWindowContainer topChildWC = new TestWindowContainer(mWm, true /* waiter */); + TestWindowContainer botChildWC = new TestWindowContainer(mWm, true /* waiter */); + parentWC.addChild(topChildWC, POSITION_TOP); + parentWC.addChild(botChildWC, POSITION_BOTTOM); + + BLASTSyncEngine bse = new BLASTSyncEngine(mWm); + + BLASTSyncEngine.TransactionReadyListener listener = mock( + BLASTSyncEngine.TransactionReadyListener.class); + + int id = bse.startSyncSet(listener); + bse.addToSyncSet(id, parentWC); + bse.setReady(id); + bse.onSurfacePlacement(); + verify(listener, times(0)).onTransactionReady(anyInt(), any()); + + parentWC.onSyncFinishedDrawing(); + topChildWC.onSyncFinishedDrawing(); + bse.onSurfacePlacement(); + verify(listener, times(0)).onTransactionReady(anyInt(), any()); + + // reparent out cancels + botChildWC.reparent(nonMemberParentWC, POSITION_TOP); + assertEquals(SYNC_STATE_NONE, botChildWC.mSyncState); + + bse.onSurfacePlacement(); + verify(listener, times(1)).onTransactionReady(eq(id), notNull()); + assertEquals(SYNC_STATE_NONE, parentWC.mSyncState); + assertEquals(SYNC_STATE_NONE, topChildWC.mSyncState); + } + + @Test + public void testReparentIn() { + TestWindowContainer nonMemberParentWC = new TestWindowContainer(mWm, true /* waiter */); + TestWindowContainer parentWC = new TestWindowContainer(mWm, true /* waiter */); + TestWindowContainer topChildWC = new TestWindowContainer(mWm, true /* waiter */); + TestWindowContainer botChildWC = new TestWindowContainer(mWm, true /* waiter */); + parentWC.addChild(topChildWC, POSITION_TOP); + nonMemberParentWC.addChild(botChildWC, POSITION_BOTTOM); + + BLASTSyncEngine bse = new BLASTSyncEngine(mWm); + + BLASTSyncEngine.TransactionReadyListener listener = mock( + BLASTSyncEngine.TransactionReadyListener.class); + + int id = bse.startSyncSet(listener); + bse.addToSyncSet(id, parentWC); + bse.setReady(id); + bse.onSurfacePlacement(); + verify(listener, times(0)).onTransactionReady(anyInt(), any()); + + parentWC.onSyncFinishedDrawing(); + topChildWC.onSyncFinishedDrawing(); + + // No-longer finished because new child + botChildWC.reparent(parentWC, POSITION_BOTTOM); + bse.onSurfacePlacement(); + verify(listener, times(0)).onTransactionReady(anyInt(), any()); + + botChildWC.onSyncFinishedDrawing(); + bse.onSurfacePlacement(); + verify(listener, times(1)).onTransactionReady(eq(id), notNull()); + assertEquals(SYNC_STATE_NONE, parentWC.mSyncState); + assertEquals(SYNC_STATE_NONE, topChildWC.mSyncState); + assertEquals(SYNC_STATE_NONE, botChildWC.mSyncState); + } + + @Test + public void testRemoval() { + // Need different transactions to verify stuff + mWm.mTransactionFactory = () -> spy(new StubTransaction()); + TestWindowContainer rootWC = new TestWindowContainer(mWm, false /* waiter */); + TestWindowContainer parentWC = new TestWindowContainer(mWm, true /* waiter */); + TestWindowContainer topChildWC = new TestWindowContainer(mWm, true /* waiter */); + TestWindowContainer botChildWC = new TestWindowContainer(mWm, true /* waiter */); + rootWC.addChild(parentWC, POSITION_TOP); + parentWC.addChild(topChildWC, POSITION_TOP); + parentWC.addChild(botChildWC, POSITION_BOTTOM); + + BLASTSyncEngine bse = new BLASTSyncEngine(mWm); + + BLASTSyncEngine.TransactionReadyListener listener = mock( + BLASTSyncEngine.TransactionReadyListener.class); + + int id = bse.startSyncSet(listener); + bse.addToSyncSet(id, parentWC); + final BLASTSyncEngine.SyncGroup syncGroup = parentWC.mSyncGroup; + bse.setReady(id); + bse.onSurfacePlacement(); + verify(listener, times(0)).onTransactionReady(anyInt(), any()); + + parentWC.onSyncFinishedDrawing(); + topChildWC.removeImmediately(); + bse.onSurfacePlacement(); + verify(listener, times(0)).onTransactionReady(anyInt(), any()); + + // Removal should merge transaction into parent + verify(parentWC.mSyncTransaction, times(1)).merge(eq(topChildWC.mSyncTransaction)); + assertEquals(SYNC_STATE_NONE, topChildWC.mSyncState); + + // Removal of a sync-root should merge transaction into orphan + parentWC.removeImmediately(); + final SurfaceControl.Transaction orphan = syncGroup.getOrphanTransaction(); + verify(orphan, times(1)).merge(eq(parentWC.mSyncTransaction)); + + // Then the orphan transaction should be merged into sync + bse.onSurfacePlacement(); + final ArgumentCaptor merged = + ArgumentCaptor.forClass(SurfaceControl.Transaction.class); + verify(listener, times(1)).onTransactionReady(eq(id), merged.capture()); + final SurfaceControl.Transaction mergedTransaction = merged.getValue(); + verify(mergedTransaction, times(1)).merge(eq(orphan)); + + assertEquals(SYNC_STATE_NONE, parentWC.mSyncState); + assertEquals(SYNC_STATE_NONE, botChildWC.mSyncState); + } + + static class TestWindowContainer extends WindowContainer { + final boolean mWaiter; + boolean mVisibleRequested = true; + boolean mFillsParent = false; + + TestWindowContainer(WindowManagerService wms, boolean waiter) { + super(wms); + mWaiter = waiter; + mDisplayContent = wms.getDefaultDisplayContentLocked(); + } + + @Override + boolean prepareSync() { + if (!super.prepareSync()) { + return false; + } + if (mWaiter) { + mSyncState = SYNC_STATE_WAITING_FOR_DRAW; + } + return true; + } + + @Override + void createSurfaceControl(boolean force) { + // nothing + } + + @Override + boolean isVisibleRequested() { + return mVisibleRequested; + } + + @Override + boolean fillsParent() { + return mFillsParent; + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java index aac83974eb512..7a1f65a3b62cf 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -37,13 +37,13 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static com.android.server.wm.DisplayArea.Type.ABOVE_TASKS; import static com.android.server.wm.WindowContainer.POSITION_TOP; +import static com.android.server.wm.WindowContainer.SYNC_STATE_READY; import static com.google.common.truth.Truth.assertThat; @@ -724,154 +724,35 @@ public class WindowOrganizerTests extends WindowTestsBase { } @Test - public void testTrivialBLASTCallback() throws RemoteException { + public void testBLASTCallbackWithActivityChildren() { final Task stackController1 = createStack(); final Task task = createTask(stackController1); - final ITaskOrganizer organizer = registerMockOrganizer(); - - spyOn(task); - doReturn(true).when(task).isVisible(); - - BLASTSyncEngine bse = new BLASTSyncEngine(); - - BLASTSyncEngine.TransactionReadyListener transactionListener = - mock(BLASTSyncEngine.TransactionReadyListener.class); - - int id = bse.startSyncSet(transactionListener); - bse.addToSyncSet(id, task); - bse.setReady(id); - // Since this task has no windows the sync is trivial and completes immediately. - verify(transactionListener) - .onTransactionReady(anyInt(), any()); - } - - @Test - public void testOverlappingBLASTCallback() throws RemoteException { - final Task stackController1 = createStack(); - final Task task = createTask(stackController1); - final ITaskOrganizer organizer = registerMockOrganizer(); - - spyOn(task); - doReturn(true).when(task).isVisible(); - final WindowState w = createAppWindow(task, TYPE_APPLICATION, "Enlightened Window"); - makeWindowVisible(w); - - BLASTSyncEngine bse = new BLASTSyncEngine(); - - BLASTSyncEngine.TransactionReadyListener transactionListener = - mock(BLASTSyncEngine.TransactionReadyListener.class); - - int id = bse.startSyncSet(transactionListener); - assertEquals(true, bse.addToSyncSet(id, task)); - bse.setReady(id); - - int id2 = bse.startSyncSet(transactionListener); - // We should be rejected from the second sync since we are already - // in one. - assertEquals(false, bse.addToSyncSet(id2, task)); - w.immediatelyNotifyBlastSync(); - assertEquals(true, bse.addToSyncSet(id2, task)); - bse.setReady(id2); - } - - @Test - public void testBLASTCallbackWithWindow() { - final Task stackController1 = createStack(); - final Task task = createTask(stackController1); - final ITaskOrganizer organizer = registerMockOrganizer(); - final WindowState w = createAppWindow(task, TYPE_APPLICATION, "Enlightened Window"); - makeWindowVisible(w); - - BLASTSyncEngine bse = new BLASTSyncEngine(); - - BLASTSyncEngine.TransactionReadyListener transactionListener = - mock(BLASTSyncEngine.TransactionReadyListener.class); - - int id = bse.startSyncSet(transactionListener); - bse.addToSyncSet(id, task); - bse.setReady(id); - // Since we have a window we have to wait for it to draw to finish sync. - verify(transactionListener, never()) - .onTransactionReady(anyInt(), any()); - w.immediatelyNotifyBlastSync(); - verify(transactionListener) - .onTransactionReady(anyInt(), any()); - } - - @Test - public void testBLASTCallbackNoDoubleAdd() { - final Task stackController1 = createStack(); - final Task task = createTask(stackController1); - final ITaskOrganizer organizer = registerMockOrganizer(); - final WindowState w = createAppWindow(task, TYPE_APPLICATION, "Enlightened Window"); - makeWindowVisible(w); - - BLASTSyncEngine bse = new BLASTSyncEngine(); - - BLASTSyncEngine.TransactionReadyListener transactionListener = - mock(BLASTSyncEngine.TransactionReadyListener.class); - - int id = bse.startSyncSet(transactionListener); - assertTrue(bse.addToSyncSet(id, w)); - assertFalse(bse.addToSyncSet(id, w)); - - // Clean-up - bse.setReady(id); - } - - - @Test - public void testBLASTCallbackWithInvisibleWindow() { - final Task stackController1 = createStack(); - final Task task = createTask(stackController1); - final ITaskOrganizer organizer = registerMockOrganizer(); final WindowState w = createAppWindow(task, TYPE_APPLICATION, "Enlightened Window"); - BLASTSyncEngine bse = new BLASTSyncEngine(); - - BLASTSyncEngine.TransactionReadyListener transactionListener = - mock(BLASTSyncEngine.TransactionReadyListener.class); - - int id = bse.startSyncSet(transactionListener); - bse.addToSyncSet(id, task); - bse.setReady(id); - - // Since the window was invisible, the Task had no visible leaves and the sync should - // complete as soon as we call setReady. - verify(transactionListener) - .onTransactionReady(anyInt(), any()); - } - - @Test - public void testBLASTCallbackWithChildWindow() { - final Task stackController1 = createStack(); - final Task task = createTask(stackController1); - final ITaskOrganizer organizer = registerMockOrganizer(); - final WindowState w = createAppWindow(task, TYPE_APPLICATION, "Enlightened Window"); - final WindowState child = createWindow(w, TYPE_APPLICATION, "Other Window"); - + w.mActivityRecord.mVisibleRequested = true; w.mActivityRecord.setVisible(true); - makeWindowVisible(w, child); - BLASTSyncEngine bse = new BLASTSyncEngine(); + BLASTSyncEngine bse = new BLASTSyncEngine(mWm); BLASTSyncEngine.TransactionReadyListener transactionListener = mock(BLASTSyncEngine.TransactionReadyListener.class); int id = bse.startSyncSet(transactionListener); - assertEquals(true, bse.addToSyncSet(id, task)); + bse.addToSyncSet(id, task); bse.setReady(id); - w.immediatelyNotifyBlastSync(); + bse.onSurfacePlacement(); + // Even though w is invisible (and thus activity isn't waiting on it), activity will + // continue to wait until it has at-least 1 visible window. // Since we have a child window we still shouldn't be done. - verify(transactionListener, never()) - .onTransactionReady(anyInt(), any()); - reset(transactionListener); + verify(transactionListener, never()).onTransactionReady(anyInt(), any()); - child.immediatelyNotifyBlastSync(); - // Ah finally! Done - verify(transactionListener) - .onTransactionReady(anyInt(), any()); + makeWindowVisible(w); + bse.onSurfacePlacement(); + w.immediatelyNotifyBlastSync(); + bse.onSurfacePlacement(); + + verify(transactionListener).onTransactionReady(anyInt(), any()); } class StubOrganizer extends ITaskOrganizer.Stub { @@ -1028,8 +909,7 @@ public class WindowOrganizerTests extends WindowTestsBase { } @Test - public void testBLASTCallbackWithMultipleWindows() throws Exception { - final ITaskOrganizer organizer = registerMockOrganizer(); + public void testBLASTCallbackWithWindows() throws Exception { final Task stackController = createStack(); final Task task = createTask(stackController); final WindowState w1 = createAppWindow(task, TYPE_APPLICATION, "Enlightened Window 1"); @@ -1048,13 +928,20 @@ public class WindowOrganizerTests extends WindowTestsBase { verify(mockCallback, never()).onTransactionReady(anyInt(), any()); assertTrue(w1.useBLASTSync()); assertTrue(w2.useBLASTSync()); - w1.immediatelyNotifyBlastSync(); + // Make second (bottom) ready. If we started with the top, since activities fillsParent + // by default, the sync would be considered finished. + w2.immediatelyNotifyBlastSync(); + mWm.mSyncEngine.onSurfacePlacement(); + verify(mockCallback, never()).onTransactionReady(anyInt(), any()); + + assertEquals(SYNC_STATE_READY, w2.mSyncState); // Even though one Window finished drawing, both windows should still be using blast sync assertTrue(w1.useBLASTSync()); assertTrue(w2.useBLASTSync()); - w2.immediatelyNotifyBlastSync(); + w1.immediatelyNotifyBlastSync(); + mWm.mSyncEngine.onSurfacePlacement(); verify(mockCallback).onTransactionReady(anyInt(), any()); assertFalse(w1.useBLASTSync()); assertFalse(w2.useBLASTSync()); 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 c18043fcc4b99..5010cacc40efb 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -46,6 +46,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.server.wm.WindowContainer.SYNC_STATE_WAITING_FOR_DRAW; import static com.google.common.truth.Truth.assertThat; @@ -608,7 +609,8 @@ public class WindowStateTests extends WindowTestsBase { // Check that the window is in resizing if using blast sync. win.reportResized(); - win.prepareForSync(mock(BLASTSyncEngine.TransactionReadyListener.class), 1); + win.prepareSync(); + assertEquals(SYNC_STATE_WAITING_FOR_DRAW, win.mSyncState); win.updateResizingWindowIfNeeded(); assertThat(mWm.mResizingWindows).contains(win);