• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
20 
21 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SYNC_ENGINE;
22 import static com.android.server.wm.WindowState.BLAST_TIMEOUT_DURATION;
23 
24 import android.annotation.NonNull;
25 import android.annotation.Nullable;
26 import android.os.Trace;
27 import android.util.ArraySet;
28 import android.util.Slog;
29 import android.util.SparseArray;
30 import android.view.SurfaceControl;
31 
32 import com.android.internal.annotations.VisibleForTesting;
33 import com.android.internal.protolog.common.ProtoLog;
34 
35 import java.util.ArrayList;
36 
37 /**
38  * Utility class for collecting WindowContainers that will merge transactions.
39  * For example to use to synchronously resize all the children of a window container
40  *   1. Open a new sync set, and pass the listener that will be invoked
41  *        int id startSyncSet(TransactionReadyListener)
42  *      the returned ID will be eventually passed to the TransactionReadyListener in combination
43  *      with a set of WindowContainers that are ready, meaning onTransactionReady was called for
44  *      those WindowContainers. You also use it to refer to the operation in future steps.
45  *   2. Ask each child to participate:
46  *       addToSyncSet(int id, WindowContainer wc)
47  *      if the child thinks it will be affected by a configuration change (a.k.a. has a visible
48  *      window in its sub hierarchy, then we will increment a counter of expected callbacks
49  *      At this point the containers hierarchy will redirect pendingTransaction and sub hierarchy
50  *      updates in to the sync engine.
51  *   3. Apply your configuration changes to the window containers.
52  *   4. Tell the engine that the sync set is ready
53  *       setReady(int id)
54  *   5. If there were no sub windows anywhere in the hierarchy to wait on, then
55  *      transactionReady is immediately invoked, otherwise all the windows are poked
56  *      to redraw and to deliver a buffer to {@link WindowState#finishDrawing}.
57  *      Once all this drawing is complete, all the transactions will be merged and delivered
58  *      to TransactionReadyListener.
59  *
60  * This works primarily by setting-up state and then watching/waiting for the registered subtrees
61  * to enter into a "finished" state (either by receiving drawn content or by disappearing). This
62  * checks the subtrees during surface-placement.
63  */
64 class BLASTSyncEngine {
65     private static final String TAG = "BLASTSyncEngine";
66 
67     /** No specific method. Used by override specifiers. */
68     public static final int METHOD_UNDEFINED = -1;
69 
70     /** No sync method. Apps will draw/present internally and just report. */
71     public static final int METHOD_NONE = 0;
72 
73     /** Sync with BLAST. Apps will draw and then send the buffer to be applied in sync. */
74     public static final int METHOD_BLAST = 1;
75 
76     interface TransactionReadyListener {
onTransactionReady(int mSyncId, SurfaceControl.Transaction transaction)77         void onTransactionReady(int mSyncId, SurfaceControl.Transaction transaction);
78     }
79 
80     /**
81      * Represents the desire to make a {@link BLASTSyncEngine.SyncGroup} while another is active.
82      *
83      * @see #queueSyncSet
84      */
85     private static class PendingSyncSet {
86         /** Called immediately when the {@link BLASTSyncEngine} is free. */
87         private Runnable mStartSync;
88 
89         /** Posted to the main handler after {@link #mStartSync} is called. */
90         private Runnable mApplySync;
91     }
92 
93     /**
94      * Holds state associated with a single synchronous set of operations.
95      */
96     class SyncGroup {
97         final int mSyncId;
98         final int mSyncMethod;
99         final TransactionReadyListener mListener;
100         final Runnable mOnTimeout;
101         boolean mReady = false;
102         final ArraySet<WindowContainer> mRootMembers = new ArraySet<>();
103         private SurfaceControl.Transaction mOrphanTransaction = null;
104         private String mTraceName;
105 
SyncGroup(TransactionReadyListener listener, int id, String name, int method)106         private SyncGroup(TransactionReadyListener listener, int id, String name, int method) {
107             mSyncId = id;
108             mSyncMethod = method;
109             mListener = listener;
110             mOnTimeout = () -> {
111                 Slog.w(TAG, "Sync group " + mSyncId + " timeout");
112                 synchronized (mWm.mGlobalLock) {
113                     onTimeout();
114                 }
115             };
116             if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
117                 mTraceName = name + "SyncGroupReady";
118                 Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, mTraceName, id);
119             }
120         }
121 
122         /**
123          * Gets a transaction to dump orphaned operations into. Orphaned operations are operations
124          * that were on the mSyncTransactions of "root" subtrees which have been removed during the
125          * sync period.
126          */
127         @NonNull
getOrphanTransaction()128         SurfaceControl.Transaction getOrphanTransaction() {
129             if (mOrphanTransaction == null) {
130                 // Lazy since this isn't common
131                 mOrphanTransaction = mWm.mTransactionFactory.get();
132             }
133             return mOrphanTransaction;
134         }
135 
onSurfacePlacement()136         private void onSurfacePlacement() {
137             if (!mReady) return;
138             ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: onSurfacePlacement checking %s",
139                     mSyncId, mRootMembers);
140             for (int i = mRootMembers.size() - 1; i >= 0; --i) {
141                 final WindowContainer wc = mRootMembers.valueAt(i);
142                 if (!wc.isSyncFinished()) {
143                     ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d:  Unfinished container: %s",
144                             mSyncId, wc);
145                     return;
146                 }
147             }
148             finishNow();
149         }
150 
finishNow()151         private void finishNow() {
152             if (mTraceName != null) {
153                 Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, mTraceName, mSyncId);
154             }
155             ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Finished!", mSyncId);
156             SurfaceControl.Transaction merged = mWm.mTransactionFactory.get();
157             if (mOrphanTransaction != null) {
158                 merged.merge(mOrphanTransaction);
159             }
160             for (WindowContainer wc : mRootMembers) {
161                 wc.finishSync(merged, false /* cancel */);
162             }
163 
164             final ArraySet<WindowContainer> wcAwaitingCommit = new ArraySet<>();
165             for (WindowContainer wc : mRootMembers) {
166                 wc.waitForSyncTransactionCommit(wcAwaitingCommit);
167             }
168             class CommitCallback implements Runnable {
169                 // Can run a second time if the action completes after the timeout.
170                 boolean ran = false;
171                 public void onCommitted() {
172                     synchronized (mWm.mGlobalLock) {
173                         if (ran) {
174                             return;
175                         }
176                         mWm.mH.removeCallbacks(this);
177                         ran = true;
178                         SurfaceControl.Transaction t = new SurfaceControl.Transaction();
179                         for (WindowContainer wc : wcAwaitingCommit) {
180                             wc.onSyncTransactionCommitted(t);
181                         }
182                         t.apply();
183                         wcAwaitingCommit.clear();
184                     }
185                 }
186 
187                 // Called in timeout
188                 @Override
189                 public void run() {
190                     // Sometimes we get a trace, sometimes we get a bugreport without
191                     // a trace. Since these kind of ANRs can trigger such an issue,
192                     // try and ensure we will have some visibility in both cases.
193                     Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "onTransactionCommitTimeout");
194                     Slog.e(TAG, "WM sent Transaction to organized, but never received" +
195                            " commit callback. Application ANR likely to follow.");
196                     Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
197                     onCommitted();
198 
199                 }
200             };
201             CommitCallback callback = new CommitCallback();
202             merged.addTransactionCommittedListener((r) -> { r.run(); }, callback::onCommitted);
203             mWm.mH.postDelayed(callback, BLAST_TIMEOUT_DURATION);
204 
205             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "onTransactionReady");
206             mListener.onTransactionReady(mSyncId, merged);
207             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
208             mActiveSyncs.remove(mSyncId);
209             mWm.mH.removeCallbacks(mOnTimeout);
210 
211             // Immediately start the next pending sync-transaction if there is one.
212             if (mActiveSyncs.size() == 0 && !mPendingSyncSets.isEmpty()) {
213                 ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "PendingStartTransaction found");
214                 final PendingSyncSet pt = mPendingSyncSets.remove(0);
215                 pt.mStartSync.run();
216                 if (mActiveSyncs.size() == 0) {
217                     throw new IllegalStateException("Pending Sync Set didn't start a sync.");
218                 }
219                 // Post this so that the now-playing transition setup isn't interrupted.
220                 mWm.mH.post(() -> {
221                     synchronized (mWm.mGlobalLock) {
222                         pt.mApplySync.run();
223                     }
224                 });
225             }
226         }
227 
setReady(boolean ready)228         private void setReady(boolean ready) {
229             if (mReady == ready) {
230                 return;
231             }
232             ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Set ready", mSyncId);
233             mReady = ready;
234             if (!ready) return;
235             mWm.mWindowPlacerLocked.requestTraversal();
236         }
237 
addToSync(WindowContainer wc)238         private void addToSync(WindowContainer wc) {
239             if (!mRootMembers.add(wc)) {
240                 return;
241             }
242             ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Adding to group: %s", mSyncId, wc);
243             wc.setSyncGroup(this);
244             wc.prepareSync();
245             if (mReady) {
246                 mWm.mWindowPlacerLocked.requestTraversal();
247             }
248         }
249 
onCancelSync(WindowContainer wc)250         void onCancelSync(WindowContainer wc) {
251             mRootMembers.remove(wc);
252         }
253 
onTimeout()254         private void onTimeout() {
255             if (!mActiveSyncs.contains(mSyncId)) return;
256             boolean allFinished = true;
257             for (int i = mRootMembers.size() - 1; i >= 0; --i) {
258                 final WindowContainer<?> wc = mRootMembers.valueAt(i);
259                 if (!wc.isSyncFinished()) {
260                     allFinished = false;
261                     Slog.i(TAG, "Unfinished container: " + wc);
262                 }
263             }
264             if (allFinished && !mReady) {
265                 Slog.w(TAG, "Sync group " + mSyncId + " timed-out because not ready. If you see "
266                         + "this, please file a bug.");
267             }
268             finishNow();
269         }
270     }
271 
272     private final WindowManagerService mWm;
273     private int mNextSyncId = 0;
274     private final SparseArray<SyncGroup> mActiveSyncs = new SparseArray<>();
275 
276     /**
277      * A queue of pending sync-sets waiting for their turn to run.
278      *
279      * @see #queueSyncSet
280      */
281     private final ArrayList<PendingSyncSet> mPendingSyncSets = new ArrayList<>();
282 
BLASTSyncEngine(WindowManagerService wms)283     BLASTSyncEngine(WindowManagerService wms) {
284         mWm = wms;
285     }
286 
287     /**
288      * Prepares a {@link SyncGroup} that is not active yet. Caller must call {@link #startSyncSet}
289      * before calling {@link #addToSyncSet(int, WindowContainer)} on any {@link WindowContainer}.
290      */
prepareSyncSet(TransactionReadyListener listener, String name, int method)291     SyncGroup prepareSyncSet(TransactionReadyListener listener, String name, int method) {
292         return new SyncGroup(listener, mNextSyncId++, name, method);
293     }
294 
startSyncSet(TransactionReadyListener listener, long timeoutMs, String name, int method)295     int startSyncSet(TransactionReadyListener listener, long timeoutMs, String name,
296             int method) {
297         final SyncGroup s = prepareSyncSet(listener, name, method);
298         startSyncSet(s, timeoutMs);
299         return s.mSyncId;
300     }
301 
startSyncSet(SyncGroup s)302     void startSyncSet(SyncGroup s) {
303         startSyncSet(s, BLAST_TIMEOUT_DURATION);
304     }
305 
startSyncSet(SyncGroup s, long timeoutMs)306     void startSyncSet(SyncGroup s, long timeoutMs) {
307         if (mActiveSyncs.size() != 0) {
308             // We currently only support one sync at a time, so start a new SyncGroup when there is
309             // another may cause issue.
310             ProtoLog.w(WM_DEBUG_SYNC_ENGINE,
311                     "SyncGroup %d: Started when there is other active SyncGroup", s.mSyncId);
312         }
313         mActiveSyncs.put(s.mSyncId, s);
314         ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Started for listener: %s",
315                 s.mSyncId, s.mListener);
316         scheduleTimeout(s, timeoutMs);
317     }
318 
319     @Nullable
getSyncSet(int id)320     SyncGroup getSyncSet(int id) {
321         return mActiveSyncs.get(id);
322     }
323 
hasActiveSync()324     boolean hasActiveSync() {
325         return mActiveSyncs.size() != 0;
326     }
327 
328     @VisibleForTesting
scheduleTimeout(SyncGroup s, long timeoutMs)329     void scheduleTimeout(SyncGroup s, long timeoutMs) {
330         mWm.mH.postDelayed(s.mOnTimeout, timeoutMs);
331     }
332 
addToSyncSet(int id, WindowContainer wc)333     void addToSyncSet(int id, WindowContainer wc) {
334         getSyncGroup(id).addToSync(wc);
335     }
336 
setReady(int id, boolean ready)337     void setReady(int id, boolean ready) {
338         getSyncGroup(id).setReady(ready);
339     }
340 
setReady(int id)341     void setReady(int id) {
342         setReady(id, true);
343     }
344 
isReady(int id)345     boolean isReady(int id) {
346         return getSyncGroup(id).mReady;
347     }
348 
349     /**
350      * Aborts the sync (ie. it doesn't wait for ready or anything to finish)
351      */
abort(int id)352     void abort(int id) {
353         getSyncGroup(id).finishNow();
354     }
355 
getSyncGroup(int id)356     private SyncGroup getSyncGroup(int id) {
357         final SyncGroup syncGroup = mActiveSyncs.get(id);
358         if (syncGroup == null) {
359             throw new IllegalStateException("SyncGroup is not started yet id=" + id);
360         }
361         return syncGroup;
362     }
363 
onSurfacePlacement()364     void onSurfacePlacement() {
365         // backwards since each state can remove itself if finished
366         for (int i = mActiveSyncs.size() - 1; i >= 0; --i) {
367             mActiveSyncs.valueAt(i).onSurfacePlacement();
368         }
369     }
370 
371     /**
372      * Queues a sync operation onto this engine. It will wait until any current/prior sync-sets
373      * have finished to run. This is needed right now because currently {@link BLASTSyncEngine}
374      * only supports 1 sync at a time.
375      *
376      * Code-paths should avoid using this unless absolutely necessary. Usually, we use this for
377      * difficult edge-cases that we hope to clean-up later.
378      *
379      * @param startSync will be called immediately when the {@link BLASTSyncEngine} is free to
380      *                  "reserve" the {@link BLASTSyncEngine} by calling one of the
381      *                  {@link BLASTSyncEngine#startSyncSet} variants.
382      * @param applySync will be posted to the main handler after {@code startSync} has been
383      *                  called. This is posted so that it doesn't interrupt any clean-up for the
384      *                  prior sync-set.
385      */
queueSyncSet(@onNull Runnable startSync, @NonNull Runnable applySync)386     void queueSyncSet(@NonNull Runnable startSync, @NonNull Runnable applySync) {
387         final PendingSyncSet pt = new PendingSyncSet();
388         pt.mStartSync = startSync;
389         pt.mApplySync = applySync;
390         mPendingSyncSets.add(pt);
391     }
392 
393     /** @return {@code true} if there are any sync-sets waiting to start. */
hasPendingSyncSets()394     boolean hasPendingSyncSets() {
395         return !mPendingSyncSets.isEmpty();
396     }
397 }
398