Introduce BLASTSyncEngine

Test: wmtests/TaskOrganizerTests, TaskOrganizerTests demo apps
Change-Id: I82c2e184e93782549bc4f78e0986a026d7ef8d03
This commit is contained in:
Robert Carr
2019-11-11 15:03:15 -08:00
parent b41e7e8fe4
commit e10ee3d2eb
9 changed files with 320 additions and 13 deletions

View File

@@ -31,8 +31,19 @@ interface ITaskOrganizerController {
*/
void registerTaskOrganizer(ITaskOrganizer organizer, int windowingMode);
/** Apply multiple WindowContainer operations at once. */
void applyContainerTransaction(in WindowContainerTransaction t);
/**
* Apply multiple WindowContainer operations at once.
* @param organizer If non-null this transaction will use the synchronization
* scheme described in BLASTSyncEngine.java. The SurfaceControl transaction
* containing the effects of this WindowContainer transaction will be passed
* to the organizers Transaction ready callback. If null the transaction
* will apply with non particular synchronization constraints (other than
* it will all apply at once).
* @return If organizer was non-null returns an ID for the sync operation which will
* later be passed to transactionReady. This lets TaskOrganizer implementations
* differentiate overlapping sync operations.
*/
int applyContainerTransaction(in WindowContainerTransaction t, ITaskOrganizer organizer);
/** Creates a persistent root task in WM for a particular windowing-mode. */
ActivityManager.RunningTaskInfo createRootTask(int displayId, int windowingMode);

View File

@@ -0,0 +1,122 @@
/*
* 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 android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
import android.view.ITaskOrganizer;
import android.view.SurfaceControl;
import java.util.HashMap;
/**
* Utility class for collecting and merging transactions from various sources asynchronously.
* For example to use to synchronously resize all the children of a window container
* 1. Open a new sync set, and pass the listener that will be invoked
* int id startSyncSet(TransactionReadyListener)
* the returned ID will be eventually passed to the TransactionReadyListener in combination
* with the prepared transaction. You also use it to refer to the operation in future steps.
* 2. Ask each child to participate:
* addToSyncSet(int id, WindowContainer wc)
* if the child thinks it will be affected by a configuration change (a.k.a. has a visible
* window in its sub hierarchy, then we will increment a counter of expected callbacks
* At this point the containers hierarchy will redirect pendingTransaction and sub hierarchy
* updates in to the sync engine.
* 3. Apply your configuration changes to the window containers.
* 4. Tell the engine that the sync set is ready
* setReady(int id)
* 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 WMS#finishDrawing.
* Once all this drawing is complete the combined transaction of all the buffers
* and pending transaction hierarchy changes will be delivered to the TransactionReadyListener
*/
class BLASTSyncEngine {
private static final String TAG = "BLASTSyncEngine";
interface TransactionReadyListener {
void transactionReady(int mSyncId, SurfaceControl.Transaction mergedTransaction);
};
// Holds state associated with a single synchronous set of operations.
class SyncState implements TransactionReadyListener {
int mSyncId;
SurfaceControl.Transaction mMergedTransaction;
int mRemainingTransactions;
TransactionReadyListener mListener;
boolean mReady = false;
private void tryFinish() {
if (mRemainingTransactions == 0 && mReady) {
mListener.transactionReady(mSyncId, mMergedTransaction);
mPendingSyncs.remove(mSyncId);
}
}
public void transactionReady(int mSyncId, SurfaceControl.Transaction mergedTransaction) {
mRemainingTransactions--;
mMergedTransaction.merge(mergedTransaction);
tryFinish();
}
void setReady() {
mReady = true;
tryFinish();
}
boolean addToSync(WindowContainer wc) {
if (wc.prepareForSync(this, mSyncId)) {
mRemainingTransactions++;
return true;
}
return false;
}
SyncState(TransactionReadyListener l, int id) {
mListener = l;
mSyncId = id;
mMergedTransaction = new SurfaceControl.Transaction();
mRemainingTransactions = 0;
}
};
int mNextSyncId = 0;
final HashMap<Integer, SyncState> mPendingSyncs = new HashMap();
BLASTSyncEngine() {
}
int startSyncSet(TransactionReadyListener listener) {
final int id = mNextSyncId++;
final SyncState s = new SyncState(listener, id);
mPendingSyncs.put(id, s);
return id;
}
boolean addToSyncSet(int id, WindowContainer wc) {
final SyncState st = mPendingSyncs.get(id);
return st.addToSync(wc);
}
// TODO(b/148476626): TIMEOUTS!
void setReady(int id) {
final SyncState st = mPendingSyncs.get(id);
st.setReady();
}
}

View File

@@ -526,7 +526,9 @@ public class DisplayRotation {
mService.mH.removeCallbacks(mDisplayRotationHandlerTimeout);
mIsWaitingForRemoteRotation = false;
mDisplayContent.sendNewConfiguration();
mService.mAtmService.mTaskOrganizerController.applyContainerTransaction(t);
if (t != null) {
mService.mAtmService.mTaskOrganizerController.applyContainerTransaction(t, null);
}
}
}

View File

@@ -38,6 +38,7 @@ import android.util.ArraySet;
import android.util.Slog;
import android.view.ITaskOrganizer;
import android.view.IWindowContainer;
import android.view.SurfaceControl;
import android.view.WindowContainerTransaction;
import com.android.internal.util.function.pooled.PooledConsumer;
@@ -53,7 +54,8 @@ import java.util.WeakHashMap;
* Stores the TaskOrganizers associated with a given windowing mode and
* their associated state.
*/
class TaskOrganizerController extends ITaskOrganizerController.Stub {
class TaskOrganizerController extends ITaskOrganizerController.Stub
implements BLASTSyncEngine.TransactionReadyListener {
private static final String TAG = "TaskOrganizerController";
/** Flag indicating that an applied transaction may have effected lifecycle */
@@ -164,6 +166,8 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
private final WeakHashMap<Task, RunningTaskInfo> mLastSentTaskInfos = new WeakHashMap<>();
private final ArrayList<Task> mPendingTaskInfoChanges = new ArrayList<>();
private final BLASTSyncEngine mBLASTSyncEngine = new BLASTSyncEngine();
final ActivityTaskManagerService mService;
RunningTaskInfo mTmpTaskInfo;
@@ -223,7 +227,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
}
void onTaskAppeared(ITaskOrganizer organizer, Task task) {
TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder());
final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder());
state.addTask(task);
}
@@ -429,15 +433,35 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
}
@Override
public void applyContainerTransaction(WindowContainerTransaction t) {
public int applyContainerTransaction(WindowContainerTransaction t, ITaskOrganizer organizer) {
enforceStackPermission("applyContainerTransaction()");
int syncId = -1;
if (t == null) {
return;
throw new IllegalArgumentException(
"Null transaction passed to applyContainerTransaction");
}
long ident = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
int effects = 0;
/**
* If organizer is non-null we are looking to synchronize this transaction
* by collecting all the results in to a SurfaceFlinger transaction and
* then delivering that to the given organizers transaction ready callback.
* See {@link BLASTSyncEngine} for the details of the operation. But at
* a high level we create a sync operation with a given ID and an associated
* organizer. Then we notify each WindowContainer in this WindowContainer
* transaction that it is participating in a sync operation with that
* ID. Once everything is notified we tell the BLASTSyncEngine
* "setSyncReady" which means that we have added everything
* to the set. At any point after this, all the WindowContainers
* will eventually finish applying their changes and notify the
* BLASTSyncEngine which will deliver the Transaction to the organizer.
*/
if (organizer != null) {
syncId = startSyncWithOrganizer(organizer);
}
mService.deferWindowLayout();
try {
ArraySet<WindowContainer> haveConfigChanges = new ArraySet<>();
@@ -450,11 +474,15 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
entry.getKey()).getContainer();
int containerEffect = applyWindowContainerChange(wc, entry.getValue());
effects |= containerEffect;
// Lifecycle changes will trigger ensureConfig for everything.
if ((effects & TRANSACT_EFFECTS_LIFECYCLE) == 0
&& (containerEffect & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) {
haveConfigChanges.add(wc);
}
if (syncId >= 0) {
mBLASTSyncEngine.addToSyncSet(syncId, wc);
}
}
if ((effects & TRANSACT_EFFECTS_LIFECYCLE) != 0) {
// Already calls ensureActivityConfig
@@ -475,10 +503,38 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
}
} finally {
mService.continueWindowLayout();
if (syncId >= 0) {
setSyncReady(syncId);
}
}
}
} finally {
Binder.restoreCallingIdentity(ident);
}
return syncId;
}
@Override
public void transactionReady(int id, SurfaceControl.Transaction sc) {
final ITaskOrganizer organizer = mTaskOrganizersByPendingSyncId.get(id);
if (organizer == null) {
Slog.e(TAG, "Got transaction complete for unexpected ID");
}
try {
organizer.transactionReady(id, sc);
} catch (RemoteException e) {
}
mTaskOrganizersByPendingSyncId.remove(id);
}
int startSyncWithOrganizer(ITaskOrganizer organizer) {
int id = mBLASTSyncEngine.startSyncSet(this);
mTaskOrganizersByPendingSyncId.put(id, organizer);
return id;
}
void setSyncReady(int id) {
mBLASTSyncEngine.setReady(id);
}
}

View File

@@ -94,7 +94,8 @@ import java.util.function.Predicate;
* changes are made to this class.
*/
class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<E>
implements Comparable<WindowContainer>, Animatable {
implements Comparable<WindowContainer>, Animatable,
BLASTSyncEngine.TransactionReadyListener {
private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowContainer" : TAG_WM;
@@ -260,6 +261,12 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
*/
RemoteToken mRemoteToken = null;
BLASTSyncEngine mBLASTSyncEngine = new BLASTSyncEngine();
SurfaceControl.Transaction mBLASTSyncTransaction = new SurfaceControl.Transaction();
boolean mUsingBLASTSyncTransaction = false;
BLASTSyncEngine.TransactionReadyListener mWaitingListener;
int mWaitingSyncId;
WindowContainer(WindowManagerService wms) {
mWmService = wms;
mPendingTransaction = wms.mTransactionFactory.get();
@@ -1837,6 +1844,10 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
@Override
public Transaction getPendingTransaction() {
if (mUsingBLASTSyncTransaction) {
return mBLASTSyncTransaction;
}
final DisplayContent displayContent = getDisplayContent();
if (displayContent != null && displayContent != this) {
return displayContent.getPendingTransaction();
@@ -2316,4 +2327,38 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
return sb.toString();
}
}
@Override
public void transactionReady(int mSyncId, SurfaceControl.Transaction mergedTransaction) {
mergedTransaction.merge(mBLASTSyncTransaction);
mUsingBLASTSyncTransaction = false;
mWaitingListener.transactionReady(mWaitingSyncId, mergedTransaction);
mWaitingListener = null;
mWaitingSyncId = -1;
}
boolean prepareForSync(BLASTSyncEngine.TransactionReadyListener waitingListener,
int waitingId) {
boolean willSync = false;
if (!isVisible()) {
return willSync;
}
mUsingBLASTSyncTransaction = true;
int localId = mBLASTSyncEngine.startSyncSet(this);
for (int i = 0; i < mChildren.size(); i++) {
final WindowContainer child = mChildren.get(i);
willSync = mBLASTSyncEngine.addToSyncSet(localId, child) | willSync;
}
// Make sure to set these before we call setReady in case the sync was a no-op
mWaitingSyncId = waitingId;
mWaitingListener = waitingListener;
mBLASTSyncEngine.setReady(localId);
return willSync;
}
}

View File

@@ -2505,7 +2505,7 @@ public class WindowManagerService extends IWindowManager.Stub
WindowState win = windowForClientLocked(session, client, false);
ProtoLog.d(WM_DEBUG_ADD_REMOVE, "finishDrawingWindow: %s mDrawState=%s",
win, (win != null ? win.mWinAnimator.drawStateToString() : "null"));
if (win != null && win.mWinAnimator.finishDrawingLocked(postDrawTransaction)) {
if (win != null && win.finishDrawing(postDrawTransaction)) {
if ((win.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) {
win.getDisplayContent().pendingLayoutChanges |=
WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;

View File

@@ -5689,4 +5689,30 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
SurfaceControl getDeferTransactionBarrier() {
return mWinAnimator.getDeferTransactionBarrier();
}
@Override
boolean prepareForSync(BLASTSyncEngine.TransactionReadyListener waitingListener,
int waitingId) {
// TODO(b/148871522): Support child window
mWaitingListener = waitingListener;
mWaitingSyncId = waitingId;
mUsingBLASTSyncTransaction = true;
return true;
}
boolean finishDrawing(SurfaceControl.Transaction postDrawTransaction) {
if (!mUsingBLASTSyncTransaction) {
return mWinAnimator.finishDrawingLocked(postDrawTransaction);
}
if (postDrawTransaction == null) {
postDrawTransaction = new SurfaceControl.Transaction();
}
postDrawTransaction.merge(mBLASTSyncTransaction);
mWaitingListener.transactionReady(mWaitingSyncId, postDrawTransaction);
mUsingBLASTSyncTransaction = false;
mWaitingSyncId = 0;
mWaitingListener = null;
return mWinAnimator.finishDrawingLocked(null);
}
}

View File

@@ -26,16 +26,19 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
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.dx.mockito.inline.extended.ExtendedMockito.never;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityManager.StackInfo;
@@ -207,7 +210,7 @@ public class TaskOrganizerTests extends WindowTestsBase {
WindowContainerTransaction t = new WindowContainerTransaction();
Rect newBounds = new Rect(10, 10, 100, 100);
t.setBounds(task.mRemoteToken, new Rect(10, 10, 100, 100));
mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(t);
mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(t, null);
assertEquals(newBounds, task.getBounds());
}
@@ -222,7 +225,7 @@ public class TaskOrganizerTests extends WindowTestsBase {
assertEquals(stack.mRemoteToken, info.stackToken);
Rect newBounds = new Rect(10, 10, 100, 100);
t.setBounds(info.stackToken, new Rect(10, 10, 100, 100));
mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(t);
mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(t, null);
assertEquals(newBounds, stack.getBounds());
}
@@ -235,7 +238,7 @@ public class TaskOrganizerTests extends WindowTestsBase {
WindowContainerTransaction t = new WindowContainerTransaction();
assertTrue(task.isFocusable());
t.setFocusable(stack.mRemoteToken, false);
mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(t);
mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(t, null);
assertFalse(task.isFocusable());
}
@@ -364,4 +367,46 @@ public class TaskOrganizerTests extends WindowTestsBase {
}
return out;
}
@Test
public void testTrivialBLASTCallback() throws RemoteException {
final ActivityStack stackController1 = createTaskStackOnDisplay(mDisplayContent);
final Task task = createTaskInStack(stackController1, 0 /* userId */);
final ITaskOrganizer organizer = registerMockOrganizer();
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)
.transactionReady(anyInt(), any());
}
@Test
public void testBLASTCallbackWithWindow() {
final ActivityStack stackController1 = createTaskStackOnDisplay(mDisplayContent);
final Task task = createTaskInStack(stackController1, 0 /* userId */);
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 we have a window we have to wait for it to draw to finish sync.
verify(transactionListener, never())
.transactionReady(anyInt(), any());
w.finishDrawing(null);
verify(transactionListener)
.transactionReady(anyInt(), any());
}
}

View File

@@ -45,7 +45,7 @@ public class TaskOrganizerPipTest extends Service {
final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.scheduleFinishEnterPip(ti.token, new Rect(0, 0, PIP_WIDTH, PIP_HEIGHT));
try {
ActivityTaskManager.getTaskOrganizerController().applyContainerTransaction(wct);
ActivityTaskManager.getTaskOrganizerController().applyContainerTransaction(wct, null);
} catch (Exception e) {
}
}