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
This commit is contained in:
Evan Rosky
2020-10-13 20:40:23 -07:00
parent 97d89af95f
commit 123ca79858
13 changed files with 773 additions and 437 deletions

View File

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

View File

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

View File

@@ -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<WindowContainer> 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<WindowContainer> mWindowContainersReady = new ArraySet<>();
final ArraySet<WindowContainer> 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<WindowContainer> 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<SyncGroup> mActiveSyncs = new SparseArray<>();
private final ArrayMap<Integer, SyncState> 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();
}
}
}

View File

@@ -864,6 +864,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
mWmService.openSurfaceTransaction();
try {
applySurfaceChangesTransaction();
mWmService.mSyncEngine.onSurfacePlacement();
} catch (RuntimeException e) {
Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
} finally {

View File

@@ -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<WindowContainer, ChangeInfo> 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<WindowContainer> 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;
}

View File

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

View File

@@ -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<E extends WindowContainer> extends ConfigurationContainer<E>
implements Comparable<WindowContainer>, Animatable, SurfaceFreezer.Freezable,
BLASTSyncEngine.TransactionReadyListener {
implements Comparable<WindowContainer>, Animatable, SurfaceFreezer.Freezable {
private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowContainer" : TAG_WM;
@@ -290,16 +289,35 @@ class WindowContainer<E extends 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<E extends 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<WindowContainer> parent) {
@@ -372,6 +391,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
onDisplayChanged(mParent.mDisplayContent);
}
onParentChanged(mParent, oldParent);
onSyncReparent(oldParent, mParent);
}
}
@@ -612,6 +632,8 @@ class WindowContainer<E extends 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<E extends 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<E extends WindowContainer> extends ConfigurationContainer<
}
}
@Override
public void onTransactionReady(int mSyncId, Set<WindowContainer> 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();
}
}

View File

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

View File

@@ -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<Integer, IWindowContainerTransactionCallback>
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<WindowContainer> 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);

View File

@@ -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<WindowState> 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<WindowState> 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<WindowState> implements WindowManagerP
void removeIfPossible() {
super.removeIfPossible();
removeIfPossible(false /*keepVisibleDeadWindow*/);
immediatelyNotifyBlastSync();
}
private void removeIfPossible(boolean keepVisibleDeadWindow) {
@@ -5329,7 +5308,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
updateFrameRateSelectionPriorityIfNeeded();
mWinAnimator.prepareSurfaceLocked(SurfaceControl.getGlobalTransaction(), true);
notifyBlastSyncTransaction();
super.prepareSurfaces();
}
@@ -5760,13 +5738,6 @@ class WindowState extends WindowContainer<WindowState> 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<WindowState> 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;
}
/**

View File

@@ -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<SurfaceControl.Transaction> 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;
}
}
}

View File

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

View File

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