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