From e10ee3d2eb621d1aec10f168bfdc2152a81fb041 Mon Sep 17 00:00:00 2001 From: Robert Carr Date: Mon, 11 Nov 2019 15:03:15 -0800 Subject: [PATCH] Introduce BLASTSyncEngine Test: wmtests/TaskOrganizerTests, TaskOrganizerTests demo apps Change-Id: I82c2e184e93782549bc4f78e0986a026d7ef8d03 --- .../android/app/ITaskOrganizerController.aidl | 15 ++- .../android/server/wm/BLASTSyncEngine.java | 122 ++++++++++++++++++ .../android/server/wm/DisplayRotation.java | 4 +- .../server/wm/TaskOrganizerController.java | 64 ++++++++- .../android/server/wm/WindowContainer.java | 47 ++++++- .../server/wm/WindowManagerService.java | 2 +- .../com/android/server/wm/WindowState.java | 26 ++++ .../android/server/wm/TaskOrganizerTests.java | 51 +++++++- .../test/taskembed/TaskOrganizerPipTest.java | 2 +- 9 files changed, 320 insertions(+), 13 deletions(-) create mode 100644 services/core/java/com/android/server/wm/BLASTSyncEngine.java diff --git a/core/java/android/app/ITaskOrganizerController.aidl b/core/java/android/app/ITaskOrganizerController.aidl index 168f782d02a63..bfc42ef8848d1 100644 --- a/core/java/android/app/ITaskOrganizerController.aidl +++ b/core/java/android/app/ITaskOrganizerController.aidl @@ -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); diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java new file mode 100644 index 0000000000000..94decc792fd38 --- /dev/null +++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java @@ -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 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(); + } +} diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index efe79b36d645d..e71371a477f77 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -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); + } } } diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index 0733a72446a26..096541f57ba5c 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -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 mLastSentTaskInfos = new WeakHashMap<>(); private final ArrayList 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 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); } } diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 86723153c5f62..9acb660967cbe 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -94,7 +94,8 @@ import java.util.function.Predicate; * changes are made to this class. */ class WindowContainer extends ConfigurationContainer - implements Comparable, Animatable { + implements Comparable, Animatable, + BLASTSyncEngine.TransactionReadyListener { private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowContainer" : TAG_WM; @@ -260,6 +261,12 @@ class 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 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 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; + } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 6e1f46bb5e42d..ec6b6c43215be 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -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; diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 42e5bbc1c1ee8..b336f8d1a1ac4 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -5689,4 +5689,30 @@ class WindowState extends WindowContainer 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); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java index a9a20f675f905..7172a1b14244f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java @@ -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()); + } } diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java index 8f3cb3442f5ae..bdfaaa8f11ea1 100644 --- a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java +++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java @@ -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) { } }