• 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.WmProtoLogGroups.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.Handler;
27 import android.os.Trace;
28 import android.util.ArraySet;
29 import android.util.Slog;
30 import android.view.SurfaceControl;
31 
32 import com.android.internal.annotations.VisibleForTesting;
33 import com.android.internal.protolog.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  * By default, all Syncs will be serialized (and it is an error to start one while another is
65  * active). However, a sync can be explicitly started in "parallel". This does not guarantee that
66  * it will run in parallel; however, it will run in parallel as long as it's watched hierarchy
67  * doesn't overlap with any other syncs' watched hierarchies.
68  *
69  * Currently, a sync that is started as "parallel" implicitly ignores the subtree below it's
70  * direct members unless those members are activities (WindowStates are considered "part of" the
71  * activity). This allows "stratified" parallelism where, eg, a sync that is only at Task-level
72  * can run in parallel with another sync that includes only the task's activities.
73  *
74  * If, at any time, a container is added to a parallel sync that *is* watched by another sync, it
75  * will be forced to serialize with it. This is done by adding a dependency. A sync will only
76  * finish if it has no active dependencies. At this point it is effectively not parallel anymore.
77  *
78  * To avoid dependency cycles, if a sync B ultimately depends on a sync A and a container is added
79  * to A which is watched by B, that container will, instead, be moved from B to A instead of
80  * creating a cyclic dependency.
81  *
82  * When syncs overlap, this will attempt to finish everything in the order they were started.
83  */
84 class BLASTSyncEngine {
85     private static final String TAG = "BLASTSyncEngine";
86 
87     /** No specific method. Used by override specifiers. */
88     public static final int METHOD_UNDEFINED = -1;
89 
90     /** No sync method. Apps will draw/present internally and just report. */
91     public static final int METHOD_NONE = 0;
92 
93     /** Sync with BLAST. Apps will draw and then send the buffer to be applied in sync. */
94     public static final int METHOD_BLAST = 1;
95 
96     interface TransactionReadyListener {
onTransactionReady(int mSyncId, SurfaceControl.Transaction transaction)97         void onTransactionReady(int mSyncId, SurfaceControl.Transaction transaction);
onTransactionCommitted()98         default void onTransactionCommitted() {}
onTransactionCommitTimeout()99         default void onTransactionCommitTimeout() {}
onReadyTimeout()100         default void onReadyTimeout() {}
101 
onReadyTraceStart(String name, int id)102         default void onReadyTraceStart(String name, int id) {
103             Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, name, id);
104         }
105 
onReadyTraceEnd(String name, int id)106         default void onReadyTraceEnd(String name, int id) {
107             Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, name, id);
108         }
109     }
110 
111     /**
112      * Represents the desire to make a {@link BLASTSyncEngine.SyncGroup} while another is active.
113      *
114      * @see #queueSyncSet
115      */
116     private static class PendingSyncSet {
117         /** Called immediately when the {@link BLASTSyncEngine} is free. */
118         private Runnable mStartSync;
119 
120         /** Posted to the main handler after {@link #mStartSync} is called. */
121         private Runnable mApplySync;
122     }
123 
124     /**
125      * Holds state associated with a single synchronous set of operations.
126      */
127     class SyncGroup {
128         final int mSyncId;
129         final String mSyncName;
130         int mSyncMethod = METHOD_BLAST;
131         final TransactionReadyListener mListener;
132         final Runnable mOnTimeout;
133         boolean mReady = false;
134         final ArraySet<WindowContainer> mRootMembers = new ArraySet<>();
135         private SurfaceControl.Transaction mOrphanTransaction = null;
136         private String mTraceName;
137 
138         private static final ArrayList<SyncGroup> NO_DEPENDENCIES = new ArrayList<>();
139 
140         /**
141          * When `true`, this SyncGroup will only wait for mRootMembers to draw; otherwise,
142          * it waits for the whole subtree(s) rooted at the mRootMembers.
143          */
144         boolean mIgnoreIndirectMembers = false;
145 
146         /** List of SyncGroups that must finish before this one can. */
147         @NonNull
148         ArrayList<SyncGroup> mDependencies = NO_DEPENDENCIES;
149 
SyncGroup(TransactionReadyListener listener, int id, String name)150         private SyncGroup(TransactionReadyListener listener, int id, String name) {
151             mSyncId = id;
152             mSyncName = name;
153             mListener = listener;
154             mOnTimeout = () -> {
155                 Slog.w(TAG, "Sync group " + mSyncId + " timeout");
156                 synchronized (mWm.mGlobalLock) {
157                     onTimeout();
158                 }
159             };
160             if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
161                 mTraceName = name + "-SyncReady#" + id;
162                 listener.onReadyTraceStart(mTraceName, id);
163             }
164         }
165 
166         /**
167          * Gets a transaction to dump orphaned operations into. Orphaned operations are operations
168          * that were on the mSyncTransactions of "root" subtrees which have been removed during the
169          * sync period.
170          */
171         @NonNull
getOrphanTransaction()172         SurfaceControl.Transaction getOrphanTransaction() {
173             if (mOrphanTransaction == null) {
174                 // Lazy since this isn't common
175                 mOrphanTransaction = mWm.mTransactionFactory.get();
176             }
177             return mOrphanTransaction;
178         }
179 
180         /**
181          * Check if the sync-group ignores a particular container. This is used to allow syncs at
182          * different levels to run in parallel. The primary example is Recents while an activity
183          * sync is happening.
184          */
isIgnoring(WindowContainer wc)185         boolean isIgnoring(WindowContainer wc) {
186             // Some heuristics to avoid unnecessary work:
187             // 1. For now, require an explicit acknowledgement of potential "parallelism" across
188             //    hierarchy levels (horizontal).
189             if (!mIgnoreIndirectMembers) return false;
190             // 2. Don't check WindowStates since they are below the relevant abstraction level (
191             //    anything activity/token and above).
192             if (wc.asWindowState() != null) return false;
193             // Obviously, don't ignore anything that is directly part of this group.
194             return wc.mSyncGroup != this;
195         }
196 
197         /** @return `true` if it finished. */
tryFinish()198         private boolean tryFinish() {
199             if (!mReady) return false;
200             ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: onSurfacePlacement checking %s",
201                     mSyncId, mRootMembers);
202             if (!mDependencies.isEmpty()) {
203                 ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d:  Unfinished dependencies: %s",
204                         mSyncId, mDependencies);
205                 return false;
206             }
207             for (int i = mRootMembers.size() - 1; i >= 0; --i) {
208                 final WindowContainer wc = mRootMembers.valueAt(i);
209                 if (!wc.isSyncFinished(this)) {
210                     ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d:  Unfinished container: %s",
211                             mSyncId, wc);
212                     return false;
213                 }
214             }
215             finishNow();
216             return true;
217         }
218 
finishNow()219         private void finishNow() {
220             if (mTraceName != null) {
221                 mListener.onReadyTraceEnd(mTraceName, mSyncId);
222             }
223             ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Finished!", mSyncId);
224             SurfaceControl.Transaction merged = mWm.mTransactionFactory.get();
225             if (mOrphanTransaction != null) {
226                 merged.merge(mOrphanTransaction);
227             }
228 
229             final long mergedTxId = merged.getId();
230             class CommitCallback implements Runnable {
231                 final ArraySet<WindowContainer> mWcAwaitingCommit = new ArraySet<>();
232 
233                 // Can run a second time if the action completes after the timeout.
234                 boolean ran = false;
235                 public void onCommitted(SurfaceControl.Transaction t) {
236                     mListener.onTransactionCommitted();
237                     if (mTraceName != null) {
238                         Trace.instant(TRACE_TAG_WINDOW_MANAGER,
239                                 mSyncName + "#" + mSyncId + "-committed");
240                     }
241                     // Don't wait to hold the global lock to remove the timeout runnable
242                     mHandler.removeCallbacks(this);
243                     synchronized (mWm.mGlobalLock) {
244                         if (ran) {
245                             return;
246                         }
247                         ran = true;
248                         for (int i = mWcAwaitingCommit.size() - 1; i >= 0; --i) {
249                             mWcAwaitingCommit.valueAt(i).onSyncTransactionCommitted(t);
250                         }
251                         t.apply();
252                         mWcAwaitingCommit.clear();
253                     }
254                 }
255 
256                 // Called in timeout
257                 @Override
258                 public void run() {
259                     // Sometimes we get a trace, sometimes we get a bugreport without
260                     // a trace. Since these kind of ANRs can trigger such an issue,
261                     // try and ensure we will have some visibility in both cases.
262                     Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "onTransactionCommitTimeout");
263                     Slog.e(TAG, "WM sent Transaction (#" + mSyncId + ", " + mSyncName + ", tx="
264                             + mergedTxId + ") to organizer, but never received commit callback."
265                             + " Application ANR likely to follow.");
266                     Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
267                     synchronized (mWm.mGlobalLock) {
268                         mListener.onTransactionCommitTimeout();
269                         onCommitted(merged.mNativeObject != 0
270                                 ? merged : mWm.mTransactionFactory.get());
271                     }
272                 }
273             };
274             CommitCallback callback = new CommitCallback();
275             for (int i = mRootMembers.size() - 1; i >= 0; --i) {
276                 final WindowContainer<?> wc = mRootMembers.valueAt(i);
277                 wc.finishSync(merged, this, false /* cancel */);
278                 wc.waitForSyncTransactionCommit(callback.mWcAwaitingCommit);
279             }
280 
281             merged.addTransactionCommittedListener(Runnable::run,
282                     () -> callback.onCommitted(new SurfaceControl.Transaction()));
283             mHandler.postDelayed(callback, BLAST_TIMEOUT_DURATION);
284 
285             if (mWm.mAnimator.mPendingState == WindowAnimator.PENDING_STATE_NEED_APPLY) {
286                 // Applies pending transaction before onTransactionReady to ensure the order with
287                 // sync transaction. This is unlikely to happen unless animator thread is slow.
288                 mWm.mAnimator.applyPendingTransaction();
289             }
290             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "onTransactionReady");
291             mListener.onTransactionReady(mSyncId, merged);
292             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
293             mActiveSyncs.remove(this);
294             mHandler.removeCallbacks(mOnTimeout);
295 
296             // Immediately start the next pending sync-transaction if there is one.
297             if (mActiveSyncs.size() == 0 && !mPendingSyncSets.isEmpty()) {
298                 ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "PendingStartTransaction found");
299                 final PendingSyncSet pt = mPendingSyncSets.remove(0);
300                 pt.mStartSync.run();
301                 if (mActiveSyncs.size() == 0) {
302                     throw new IllegalStateException("Pending Sync Set didn't start a sync.");
303                 }
304                 // Post this so that the now-playing transition setup isn't interrupted.
305                 mHandler.post(() -> {
306                     synchronized (mWm.mGlobalLock) {
307                         pt.mApplySync.run();
308                     }
309                 });
310             }
311             // Notify idle listeners
312             for (int i = mOnIdleListeners.size() - 1; i >= 0; --i) {
313                 // If an idle listener adds a sync, though, then stop notifying.
314                 if (mActiveSyncs.size() > 0) break;
315                 mOnIdleListeners.get(i).run();
316             }
317         }
318 
319         /** returns true if readiness changed. */
setReady(boolean ready)320         private boolean setReady(boolean ready) {
321             if (mReady == ready) {
322                 return false;
323             }
324             ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Set ready %b", mSyncId, ready);
325             mReady = ready;
326             if (ready) {
327                 mWm.mWindowPlacerLocked.requestTraversal();
328             }
329             return true;
330         }
331 
addToSync(WindowContainer wc)332         private void addToSync(WindowContainer wc) {
333             if (mRootMembers.contains(wc)) {
334                 return;
335             }
336             ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Adding to group: %s", mSyncId, wc);
337             final SyncGroup dependency = wc.getSyncGroup();
338             if (dependency != null && dependency != this && !dependency.isIgnoring(wc)) {
339                 // This syncgroup now conflicts with another one, so the whole group now must
340                 // wait on the other group.
341                 Slog.w(TAG, "SyncGroup " + mSyncId + " conflicts with " + dependency.mSyncId
342                         + ": Making " + mSyncId + " depend on " + dependency.mSyncId);
343                 if (mDependencies.contains(dependency)) {
344                     // nothing, it's already a dependency.
345                 } else if (dependency.dependsOn(this)) {
346                     Slog.w(TAG, " Detected dependency cycle between " + mSyncId + " and "
347                             + dependency.mSyncId + ": Moving " + wc + " to " + mSyncId);
348                     // Since dependency already depends on this, make this now `wc`'s watcher
349                     if (wc.mSyncGroup == null) {
350                         wc.setSyncGroup(this);
351                     } else {
352                         // Explicit replacement.
353                         wc.mSyncGroup.mRootMembers.remove(wc);
354                         mRootMembers.add(wc);
355                         wc.mSyncGroup = this;
356                     }
357                 } else {
358                     if (mDependencies == NO_DEPENDENCIES) {
359                         mDependencies = new ArrayList<>();
360                     }
361                     mDependencies.add(dependency);
362                 }
363             } else {
364                 mRootMembers.add(wc);
365                 wc.setSyncGroup(this);
366             }
367             wc.prepareSync();
368             if (wc.mSyncState == WindowContainer.SYNC_STATE_NONE && wc.mSyncGroup != null) {
369                 Slog.w(TAG, "addToSync: unset SyncGroup " + wc.mSyncGroup.mSyncId
370                         + " for non-sync " + wc);
371                 wc.mSyncGroup = null;
372             }
373             if (mWm.mAnimator.mPendingState == WindowAnimator.PENDING_STATE_HAS_CHANGES
374                     && wc.mSyncState != WindowContainer.SYNC_STATE_NONE) {
375                 mWm.mAnimator.mPendingState = WindowAnimator.PENDING_STATE_NEED_APPLY;
376             }
377             if (mReady) {
378                 mWm.mWindowPlacerLocked.requestTraversal();
379             }
380         }
381 
dependsOn(SyncGroup group)382         private boolean dependsOn(SyncGroup group) {
383             if (mDependencies.isEmpty()) return false;
384             // BFS search with membership check. We don't expect cycle here (since this is
385             // explicitly called to avoid cycles) but just to be safe.
386             final ArrayList<SyncGroup> fringe = mTmpFringe;
387             fringe.clear();
388             fringe.add(this);
389             for (int head = 0; head < fringe.size(); ++head) {
390                 final SyncGroup next = fringe.get(head);
391                 if (next == group) {
392                     fringe.clear();
393                     return true;
394                 }
395                 for (int i = 0; i < next.mDependencies.size(); ++i) {
396                     if (fringe.contains(next.mDependencies.get(i))) continue;
397                     fringe.add(next.mDependencies.get(i));
398                 }
399             }
400             fringe.clear();
401             return false;
402         }
403 
onCancelSync(WindowContainer wc)404         void onCancelSync(WindowContainer wc) {
405             mRootMembers.remove(wc);
406         }
407 
onTimeout()408         private void onTimeout() {
409             if (!mActiveSyncs.contains(this)) return;
410             boolean allFinished = true;
411             for (int i = mRootMembers.size() - 1; i >= 0; --i) {
412                 final WindowContainer<?> wc = mRootMembers.valueAt(i);
413                 if (!wc.isSyncFinished(this)) {
414                     allFinished = false;
415                     Slog.i(TAG, "Unfinished container: " + wc);
416                     wc.forAllActivities(a -> {
417                         if (a.isVisibleRequested()) {
418                             if (a.isRelaunching()) {
419                                 Slog.i(TAG, "  " + a + " is relaunching");
420                             }
421                             a.forAllWindows(w -> {
422                                 Slog.i(TAG, "  " + w + " " + w.mWinAnimator.drawStateToString());
423                             }, true /* traverseTopToBottom */);
424                         } else if (a.mDisplayContent != null && !a.mDisplayContent
425                                 .mUnknownAppVisibilityController.allResolved()) {
426                             Slog.i(TAG, "  UnknownAppVisibility: " + a.mDisplayContent
427                                     .mUnknownAppVisibilityController.getDebugMessage());
428                         }
429                     });
430                 }
431             }
432 
433             for (int i = mDependencies.size() - 1; i >= 0; --i) {
434                 allFinished = false;
435                 Slog.i(TAG, "Unfinished dependency: " + mDependencies.get(i).mSyncId);
436             }
437             if (allFinished && !mReady) {
438                 Slog.w(TAG, "Sync group " + mSyncId + " timed-out because not ready. If you see "
439                         + "this, please file a bug.");
440                 mListener.onReadyTimeout();
441             }
442             finishNow();
443             removeFromDependencies(this);
444         }
445     }
446 
447     private final WindowManagerService mWm;
448     private final Handler mHandler;
449     private int mNextSyncId = 0;
450 
451     /** Currently active syncs. Intentionally ordered by start time. */
452     private final ArrayList<SyncGroup> mActiveSyncs = new ArrayList<>();
453 
454     /**
455      * A queue of pending sync-sets waiting for their turn to run.
456      *
457      * @see #queueSyncSet
458      */
459     private final ArrayList<PendingSyncSet> mPendingSyncSets = new ArrayList<>();
460 
461     private final ArrayList<Runnable> mOnIdleListeners = new ArrayList<>();
462 
463     private final ArrayList<SyncGroup> mTmpFinishQueue = new ArrayList<>();
464     private final ArrayList<SyncGroup> mTmpFringe = new ArrayList<>();
465 
BLASTSyncEngine(WindowManagerService wms)466     BLASTSyncEngine(WindowManagerService wms) {
467         this(wms, wms.mH);
468     }
469 
470     @VisibleForTesting
BLASTSyncEngine(WindowManagerService wms, Handler mainHandler)471     BLASTSyncEngine(WindowManagerService wms, Handler mainHandler) {
472         mWm = wms;
473         mHandler = mainHandler;
474     }
475 
476     /**
477      * Prepares a {@link SyncGroup} that is not active yet. Caller must call {@link #startSyncSet}
478      * before calling {@link #addToSyncSet(int, WindowContainer)} on any {@link WindowContainer}.
479      */
prepareSyncSet(TransactionReadyListener listener, String name)480     SyncGroup prepareSyncSet(TransactionReadyListener listener, String name) {
481         return new SyncGroup(listener, mNextSyncId++, name);
482     }
483 
startSyncSet(TransactionReadyListener listener, long timeoutMs, String name, boolean parallel)484     int startSyncSet(TransactionReadyListener listener, long timeoutMs, String name,
485             boolean parallel) {
486         final SyncGroup s = prepareSyncSet(listener, name);
487         startSyncSet(s, timeoutMs, parallel);
488         return s.mSyncId;
489     }
490 
startSyncSet(SyncGroup s)491     void startSyncSet(SyncGroup s) {
492         startSyncSet(s, BLAST_TIMEOUT_DURATION, false /* parallel */);
493     }
494 
startSyncSet(SyncGroup s, long timeoutMs, boolean parallel)495     void startSyncSet(SyncGroup s, long timeoutMs, boolean parallel) {
496         final boolean alreadyRunning = mActiveSyncs.size() > 0;
497         if (!parallel && alreadyRunning) {
498             // We only support overlapping syncs when explicitly declared `parallel`.
499             Slog.e(TAG, "SyncGroup " + s.mSyncId
500                     + ": Started when there is other active SyncGroup");
501         }
502         mActiveSyncs.add(s);
503         // For now, parallel implies this.
504         s.mIgnoreIndirectMembers = parallel;
505         ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Started %sfor listener: %s",
506                 s.mSyncId, (parallel && alreadyRunning ? "(in parallel) " : ""), s.mListener);
507         scheduleTimeout(s, timeoutMs);
508     }
509 
510     @Nullable
getSyncSet(int id)511     SyncGroup getSyncSet(int id) {
512         for (int i = 0; i < mActiveSyncs.size(); ++i) {
513             if (mActiveSyncs.get(i).mSyncId != id) continue;
514             return mActiveSyncs.get(i);
515         }
516         return null;
517     }
518 
hasActiveSync()519     boolean hasActiveSync() {
520         return mActiveSyncs.size() != 0;
521     }
522 
523     @VisibleForTesting
scheduleTimeout(SyncGroup s, long timeoutMs)524     void scheduleTimeout(SyncGroup s, long timeoutMs) {
525         mHandler.postDelayed(s.mOnTimeout, timeoutMs);
526     }
527 
addToSyncSet(int id, WindowContainer wc)528     void addToSyncSet(int id, WindowContainer wc) {
529         getSyncGroup(id).addToSync(wc);
530     }
531 
setSyncMethod(int id, int method)532     void setSyncMethod(int id, int method) {
533         final SyncGroup syncGroup = getSyncGroup(id);
534         if (!syncGroup.mRootMembers.isEmpty()) {
535             throw new IllegalStateException(
536                     "Not allow to change sync method after adding group member, id=" + id);
537         }
538         syncGroup.mSyncMethod = method;
539     }
540 
setReady(int id, boolean ready)541     boolean setReady(int id, boolean ready) {
542         return getSyncGroup(id).setReady(ready);
543     }
544 
setReady(int id)545     void setReady(int id) {
546         setReady(id, true);
547     }
548 
isReady(int id)549     boolean isReady(int id) {
550         return getSyncGroup(id).mReady;
551     }
552 
553     /**
554      * Aborts the sync (ie. it doesn't wait for ready or anything to finish)
555      */
abort(int id)556     void abort(int id) {
557         final SyncGroup group = getSyncGroup(id);
558         group.finishNow();
559         removeFromDependencies(group);
560     }
561 
getSyncGroup(int id)562     private SyncGroup getSyncGroup(int id) {
563         final SyncGroup syncGroup = getSyncSet(id);
564         if (syncGroup == null) {
565             throw new IllegalStateException("SyncGroup is not started yet id=" + id);
566         }
567         return syncGroup;
568     }
569 
570     /**
571      * Just removes `group` from any dependency lists. Does not try to evaluate anything. However,
572      * it will schedule traversals if any groups were changed in a way that could make them ready.
573      */
removeFromDependencies(SyncGroup group)574     private void removeFromDependencies(SyncGroup group) {
575         boolean anyChange = false;
576         for (int i = 0; i < mActiveSyncs.size(); ++i) {
577             final SyncGroup active = mActiveSyncs.get(i);
578             if (!active.mDependencies.remove(group)) continue;
579             if (!active.mDependencies.isEmpty()) continue;
580             anyChange = true;
581         }
582         if (!anyChange) return;
583         mWm.mWindowPlacerLocked.requestTraversal();
584     }
585 
onSurfacePlacement()586     void onSurfacePlacement() {
587         if (mActiveSyncs.isEmpty()) return;
588         // queue in-order since we want interdependent syncs to become ready in the same order they
589         // started in.
590         mTmpFinishQueue.addAll(mActiveSyncs);
591         // There shouldn't be any dependency cycles or duplicates, but add an upper-bound just
592         // in case. Assuming absolute worst case, each visit will try and revisit everything
593         // before it, so n + (n-1) + (n-2) ... = (n+1)*n/2
594         int visitBounds = ((mActiveSyncs.size() + 1) * mActiveSyncs.size()) / 2;
595         while (!mTmpFinishQueue.isEmpty()) {
596             if (visitBounds <= 0) {
597                 Slog.e(TAG, "Trying to finish more syncs than theoretically possible. This "
598                         + "should never happen. Most likely a dependency cycle wasn't detected.");
599             }
600             --visitBounds;
601             final SyncGroup group = mTmpFinishQueue.remove(0);
602             final int grpIdx = mActiveSyncs.indexOf(group);
603             // Skip if it's already finished:
604             if (grpIdx < 0) continue;
605             if (!group.tryFinish()) continue;
606             // Finished, so update dependencies of any prior groups and retry if unblocked.
607             int insertAt = 0;
608             for (int i = 0; i < mActiveSyncs.size(); ++i) {
609                 final SyncGroup active = mActiveSyncs.get(i);
610                 if (!active.mDependencies.remove(group)) continue;
611                 // Anything afterwards is already in queue.
612                 if (i >= grpIdx) continue;
613                 if (!active.mDependencies.isEmpty()) continue;
614                 // `active` became unblocked so it can finish, since it started earlier, it should
615                 // be checked next to maintain order.
616                 mTmpFinishQueue.add(insertAt, mActiveSyncs.get(i));
617                 insertAt += 1;
618             }
619         }
620     }
621 
622     /** Only use this for tests! */
tryFinishForTest(int syncId)623     void tryFinishForTest(int syncId) {
624         getSyncSet(syncId).tryFinish();
625     }
626 
627     /**
628      * Queues a sync operation onto this engine. It will wait until any current/prior sync-sets
629      * have finished to run. This is needed right now because currently {@link BLASTSyncEngine}
630      * only supports 1 sync at a time.
631      *
632      * Code-paths should avoid using this unless absolutely necessary. Usually, we use this for
633      * difficult edge-cases that we hope to clean-up later.
634      *
635      * @param startSync will be called immediately when the {@link BLASTSyncEngine} is free to
636      *                  "reserve" the {@link BLASTSyncEngine} by calling one of the
637      *                  {@link BLASTSyncEngine#startSyncSet} variants.
638      * @param applySync will be posted to the main handler after {@code startSync} has been
639      *                  called. This is posted so that it doesn't interrupt any clean-up for the
640      *                  prior sync-set.
641      */
queueSyncSet(@onNull Runnable startSync, @NonNull Runnable applySync)642     void queueSyncSet(@NonNull Runnable startSync, @NonNull Runnable applySync) {
643         final PendingSyncSet pt = new PendingSyncSet();
644         pt.mStartSync = startSync;
645         pt.mApplySync = applySync;
646         mPendingSyncSets.add(pt);
647     }
648 
649     /** @return {@code true} if there are any sync-sets waiting to start. */
hasPendingSyncSets()650     boolean hasPendingSyncSets() {
651         return !mPendingSyncSets.isEmpty();
652     }
653 
addOnIdleListener(Runnable onIdleListener)654     void addOnIdleListener(Runnable onIdleListener) {
655         mOnIdleListeners.add(onIdleListener);
656     }
657 }
658