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