1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.wm;
18 
19 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SYNC_ENGINE;
20 
21 import android.annotation.NonNull;
22 import android.util.ArraySet;
23 import android.util.Slog;
24 import android.util.SparseArray;
25 import android.view.SurfaceControl;
26 
27 import com.android.internal.annotations.VisibleForTesting;
28 import com.android.internal.protolog.common.ProtoLog;
29 
30 /**
31  * Utility class for collecting WindowContainers that will merge transactions.
32  * For example to use to synchronously resize all the children of a window container
33  *   1. Open a new sync set, and pass the listener that will be invoked
34  *        int id startSyncSet(TransactionReadyListener)
35  *      the returned ID will be eventually passed to the TransactionReadyListener in combination
36  *      with a set of WindowContainers that are ready, meaning onTransactionReady was called for
37  *      those WindowContainers. You also use it to refer to the operation in future steps.
38  *   2. Ask each child to participate:
39  *       addToSyncSet(int id, WindowContainer wc)
40  *      if the child thinks it will be affected by a configuration change (a.k.a. has a visible
41  *      window in its sub hierarchy, then we will increment a counter of expected callbacks
42  *      At this point the containers hierarchy will redirect pendingTransaction and sub hierarchy
43  *      updates in to the sync engine.
44  *   3. Apply your configuration changes to the window containers.
45  *   4. Tell the engine that the sync set is ready
46  *       setReady(int id)
47  *   5. If there were no sub windows anywhere in the hierarchy to wait on, then
48  *      transactionReady is immediately invoked, otherwise all the windows are poked
49  *      to redraw and to deliver a buffer to {@link WindowState#finishDrawing}.
50  *      Once all this drawing is complete, all the transactions will be merged and delivered
51  *      to TransactionReadyListener.
52  *
53  * This works primarily by setting-up state and then watching/waiting for the registered subtrees
54  * to enter into a "finished" state (either by receiving drawn content or by disappearing). This
55  * checks the subtrees during surface-placement.
56  */
57 class BLASTSyncEngine {
58     private static final String TAG = "BLASTSyncEngine";
59 
60     interface TransactionReadyListener {
onTransactionReady(int mSyncId, SurfaceControl.Transaction transaction)61         void onTransactionReady(int mSyncId, SurfaceControl.Transaction transaction);
62     }
63 
64     /**
65      * Holds state associated with a single synchronous set of operations.
66      */
67     class SyncGroup {
68         final int mSyncId;
69         final TransactionReadyListener mListener;
70         final Runnable mOnTimeout;
71         boolean mReady = false;
72         final ArraySet<WindowContainer> mRootMembers = new ArraySet<>();
73         private SurfaceControl.Transaction mOrphanTransaction = null;
74 
SyncGroup(TransactionReadyListener listener, int id)75         private SyncGroup(TransactionReadyListener listener, int id) {
76             mSyncId = id;
77             mListener = listener;
78             mOnTimeout = () -> {
79                 Slog.w(TAG, "Sync group " + mSyncId + " timeout");
80                 synchronized (mWm.mGlobalLock) {
81                     onTimeout();
82                 }
83             };
84         }
85 
86         /**
87          * Gets a transaction to dump orphaned operations into. Orphaned operations are operations
88          * that were on the mSyncTransactions of "root" subtrees which have been removed during the
89          * sync period.
90          */
91         @NonNull
getOrphanTransaction()92         SurfaceControl.Transaction getOrphanTransaction() {
93             if (mOrphanTransaction == null) {
94                 // Lazy since this isn't common
95                 mOrphanTransaction = mWm.mTransactionFactory.get();
96             }
97             return mOrphanTransaction;
98         }
99 
onSurfacePlacement()100         private void onSurfacePlacement() {
101             if (!mReady) return;
102             ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: onSurfacePlacement checking %s",
103                     mSyncId, mRootMembers);
104             for (int i = mRootMembers.size() - 1; i >= 0; --i) {
105                 final WindowContainer wc = mRootMembers.valueAt(i);
106                 if (!wc.isSyncFinished()) {
107                     ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d:  Unfinished container: %s",
108                             mSyncId, wc);
109                     return;
110                 }
111             }
112             finishNow();
113         }
114 
finishNow()115         private void finishNow() {
116             ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Finished!", mSyncId);
117             SurfaceControl.Transaction merged = mWm.mTransactionFactory.get();
118             if (mOrphanTransaction != null) {
119                 merged.merge(mOrphanTransaction);
120             }
121             for (WindowContainer wc : mRootMembers) {
122                 wc.finishSync(merged, false /* cancel */);
123             }
124             mListener.onTransactionReady(mSyncId, merged);
125             mActiveSyncs.remove(mSyncId);
126             mWm.mH.removeCallbacks(mOnTimeout);
127         }
128 
setReady(boolean ready)129         private void setReady(boolean ready) {
130             ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Set ready", mSyncId);
131             mReady = ready;
132             if (!ready) return;
133             mWm.mWindowPlacerLocked.requestTraversal();
134         }
135 
addToSync(WindowContainer wc)136         private void addToSync(WindowContainer wc) {
137             if (!mRootMembers.add(wc)) {
138                 return;
139             }
140             ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Adding to group: %s", mSyncId, wc);
141             wc.setSyncGroup(this);
142             wc.prepareSync();
143             mWm.mWindowPlacerLocked.requestTraversal();
144         }
145 
onCancelSync(WindowContainer wc)146         void onCancelSync(WindowContainer wc) {
147             mRootMembers.remove(wc);
148         }
149 
onTimeout()150         private void onTimeout() {
151             if (!mActiveSyncs.contains(mSyncId)) return;
152             for (int i = mRootMembers.size() - 1; i >= 0; --i) {
153                 final WindowContainer<?> wc = mRootMembers.valueAt(i);
154                 if (!wc.isSyncFinished()) {
155                     Slog.i(TAG, "Unfinished container: " + wc);
156                 }
157             }
158             finishNow();
159         }
160     }
161 
162     private final WindowManagerService mWm;
163     private int mNextSyncId = 0;
164     private final SparseArray<SyncGroup> mActiveSyncs = new SparseArray<>();
165 
BLASTSyncEngine(WindowManagerService wms)166     BLASTSyncEngine(WindowManagerService wms) {
167         mWm = wms;
168     }
169 
startSyncSet(TransactionReadyListener listener)170     int startSyncSet(TransactionReadyListener listener) {
171         return startSyncSet(listener, WindowState.BLAST_TIMEOUT_DURATION);
172     }
173 
startSyncSet(TransactionReadyListener listener, long timeoutMs)174     int startSyncSet(TransactionReadyListener listener, long timeoutMs) {
175         final int id = mNextSyncId++;
176         final SyncGroup s = new SyncGroup(listener, id);
177         mActiveSyncs.put(id, s);
178         ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Started for listener: %s", id, listener);
179         scheduleTimeout(s, timeoutMs);
180         return id;
181     }
182 
183     @VisibleForTesting
scheduleTimeout(SyncGroup s, long timeoutMs)184     void scheduleTimeout(SyncGroup s, long timeoutMs) {
185         mWm.mH.postDelayed(s.mOnTimeout, timeoutMs);
186     }
187 
addToSyncSet(int id, WindowContainer wc)188     void addToSyncSet(int id, WindowContainer wc) {
189         mActiveSyncs.get(id).addToSync(wc);
190     }
191 
setReady(int id, boolean ready)192     void setReady(int id, boolean ready) {
193         mActiveSyncs.get(id).setReady(ready);
194     }
195 
setReady(int id)196     void setReady(int id) {
197         setReady(id, true);
198     }
199 
isReady(int id)200     boolean isReady(int id) {
201         return mActiveSyncs.get(id).mReady;
202     }
203 
204     /**
205      * Aborts the sync (ie. it doesn't wait for ready or anything to finish)
206      */
abort(int id)207     void abort(int id) {
208         mActiveSyncs.get(id).finishNow();
209     }
210 
onSurfacePlacement()211     void onSurfacePlacement() {
212         // backwards since each state can remove itself if finished
213         for (int i = mActiveSyncs.size() - 1; i >= 0; --i) {
214             mActiveSyncs.valueAt(i).onSurfacePlacement();
215         }
216     }
217 }
218