1 /* 2 * Copyright (C) 2022 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 android.window; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.UiThread; 22 import android.os.Binder; 23 import android.os.BinderProxy; 24 import android.os.Build; 25 import android.os.Debug; 26 import android.os.Handler; 27 import android.os.HandlerThread; 28 import android.os.IBinder; 29 import android.os.Looper; 30 import android.os.RemoteException; 31 import android.os.Trace; 32 import android.util.ArraySet; 33 import android.util.Log; 34 import android.util.Pair; 35 import android.view.AttachedSurfaceControl; 36 import android.view.SurfaceControl.Transaction; 37 import android.view.SurfaceControlViewHost; 38 import android.view.SurfaceView; 39 import android.view.WindowManagerGlobal; 40 41 import com.android.internal.annotations.GuardedBy; 42 import com.android.internal.annotations.VisibleForTesting; 43 44 import java.util.concurrent.Executor; 45 import java.util.concurrent.atomic.AtomicInteger; 46 import java.util.function.Consumer; 47 import java.util.function.Supplier; 48 49 /** 50 * A way for data to be gathered so multiple surfaces can be synced. This is intended to be 51 * used with AttachedSurfaceControl, SurfaceView, and SurfaceControlViewHost. This allows different 52 * parts of the system to synchronize different surfaces themselves without having to manage timing 53 * of different rendering threads. 54 * This will also allow synchronization of surfaces across multiple processes. The caller can add 55 * SurfaceControlViewHosts from another process to the SurfaceSyncGroup in a different process 56 * and this clas will ensure all the surfaces are ready before applying everything together. 57 * see the <a href="https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/window/SurfaceSyncGroup.md">SurfaceSyncGroup documentation</a> 58 * </p> 59 */ 60 public final class SurfaceSyncGroup { 61 private static final String TAG = "SurfaceSyncGroup"; 62 private static final boolean DEBUG = false; 63 64 private static final int MAX_COUNT = 100; 65 66 private static final AtomicInteger sCounter = new AtomicInteger(0); 67 68 /** 69 * @hide 70 */ 71 @VisibleForTesting 72 public static final int TRANSACTION_READY_TIMEOUT = 1000 * Build.HW_TIMEOUT_MULTIPLIER; 73 74 private static Supplier<Transaction> sTransactionFactory = Transaction::new; 75 76 /** 77 * Class that collects the {@link SurfaceSyncGroup}s and notifies when all the surfaces have 78 * a frame ready. 79 */ 80 private final Object mLock = new Object(); 81 82 private final String mName; 83 84 @GuardedBy("mLock") 85 private final ArraySet<ITransactionReadyCallback> mPendingSyncs = new ArraySet<>(); 86 @GuardedBy("mLock") 87 private final Transaction mTransaction = sTransactionFactory.get(); 88 @GuardedBy("mLock") 89 private boolean mSyncReady; 90 91 @GuardedBy("mLock") 92 private boolean mFinished; 93 94 @GuardedBy("mLock") 95 private Consumer<Transaction> mTransactionReadyConsumer; 96 97 @GuardedBy("mLock") 98 private ISurfaceSyncGroup mParentSyncGroup; 99 100 @GuardedBy("mLock") 101 private final ArraySet<Pair<Executor, Runnable>> mSyncCompleteCallbacks = new ArraySet<>(); 102 103 @GuardedBy("mLock") 104 private boolean mHasWMSync; 105 106 @GuardedBy("mLock") 107 private ISurfaceSyncGroupCompletedListener mSurfaceSyncGroupCompletedListener; 108 109 /** 110 * @hide 111 */ 112 public final ISurfaceSyncGroup mISurfaceSyncGroup = new ISurfaceSyncGroupImpl(); 113 114 @GuardedBy("mLock") 115 private Runnable mAddedToSyncListener; 116 117 /** 118 * Token to identify this SurfaceSyncGroup. This is used to register the SurfaceSyncGroup in 119 * WindowManager. This token is also sent to other processes' SurfaceSyncGroup that want to be 120 * included in this SurfaceSyncGroup. 121 */ 122 private final Binder mToken = new Binder(); 123 124 private static final Object sHandlerThreadLock = new Object(); 125 @GuardedBy("sHandlerThreadLock") 126 private static HandlerThread sHandlerThread; 127 private Handler mHandler; 128 129 @GuardedBy("mLock") 130 private boolean mTimeoutAdded; 131 132 /** 133 * Disable the timeout for this SSG so it will never be set until there's an explicit call to 134 * add a timeout. 135 */ 136 @GuardedBy("mLock") 137 private boolean mTimeoutDisabled; 138 139 private final String mTrackName; 140 isLocalBinder(IBinder binder)141 private static boolean isLocalBinder(IBinder binder) { 142 return !(binder instanceof BinderProxy); 143 } 144 getSurfaceSyncGroup(ISurfaceSyncGroup iSurfaceSyncGroup)145 private static SurfaceSyncGroup getSurfaceSyncGroup(ISurfaceSyncGroup iSurfaceSyncGroup) { 146 if (iSurfaceSyncGroup instanceof ISurfaceSyncGroupImpl) { 147 return ((ISurfaceSyncGroupImpl) iSurfaceSyncGroup).getSurfaceSyncGroup(); 148 } 149 return null; 150 } 151 152 /** 153 * @hide 154 */ setTransactionFactory(Supplier<Transaction> transactionFactory)155 public static void setTransactionFactory(Supplier<Transaction> transactionFactory) { 156 sTransactionFactory = transactionFactory; 157 } 158 159 /** 160 * Starts a sync and will automatically apply the final, merged transaction. 161 * 162 * @param name Used for identifying and debugging. 163 */ SurfaceSyncGroup(@onNull String name)164 public SurfaceSyncGroup(@NonNull String name) { 165 this(name, transaction -> { 166 if (transaction != null) { 167 if (DEBUG) { 168 Log.d(TAG, "Applying transaction " + transaction); 169 } 170 transaction.apply(); 171 } 172 }); 173 } 174 175 /** 176 * Creates a sync. 177 * 178 * @param name Used for identifying and debugging. 179 * @param transactionReadyConsumer The complete callback that contains the syncId and 180 * transaction with all the sync data merged. The Transaction 181 * passed back can be null. 182 * <p> 183 * NOTE: Only should be used by ViewRootImpl 184 * @hide 185 */ SurfaceSyncGroup(String name, Consumer<Transaction> transactionReadyConsumer)186 public SurfaceSyncGroup(String name, Consumer<Transaction> transactionReadyConsumer) { 187 // sCounter is a way to give the SurfaceSyncGroup a unique name even if the name passed in 188 // is not. 189 // Avoid letting the count get too big so just reset to 0. It's unlikely that we'll have 190 // more than MAX_COUNT active syncs that have overlapping names 191 if (sCounter.get() >= MAX_COUNT) { 192 sCounter.set(0); 193 } 194 195 mName = name + "#" + sCounter.getAndIncrement(); 196 mTrackName = "SurfaceSyncGroup " + name; 197 198 mTransactionReadyConsumer = (transaction) -> { 199 if (DEBUG && transaction != null) { 200 Log.d(TAG, "Sending non null transaction " + transaction + " to callback for " 201 + mName); 202 } 203 if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { 204 Trace.instantForTrack(Trace.TRACE_TAG_VIEW, mTrackName, 205 "Final TransactionCallback with " + transaction); 206 } 207 Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode()); 208 transactionReadyConsumer.accept(transaction); 209 synchronized (mLock) { 210 // If there's a registered listener with WMS, that means we aren't actually complete 211 // until WMS notifies us that the parent has completed. 212 if (mSurfaceSyncGroupCompletedListener == null) { 213 invokeSyncCompleteCallbacks(); 214 } 215 } 216 }; 217 218 if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { 219 Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_VIEW, mTrackName, mName, hashCode()); 220 } 221 222 if (DEBUG) { 223 Log.d(TAG, "setupSync " + mName + " " + Debug.getCallers(2)); 224 } 225 } 226 227 @GuardedBy("mLock") invokeSyncCompleteCallbacks()228 private void invokeSyncCompleteCallbacks() { 229 mSyncCompleteCallbacks.forEach( 230 executorRunnablePair -> executorRunnablePair.first.execute( 231 executorRunnablePair.second)); 232 } 233 234 /** 235 * Add a {@link Runnable} to be executed when the sync completes. 236 * 237 * @param executor The Executor to invoke the Runnable on 238 * @param runnable The Runnable to get called 239 * @hide 240 */ addSyncCompleteCallback(Executor executor, Runnable runnable)241 public void addSyncCompleteCallback(Executor executor, Runnable runnable) { 242 synchronized (mLock) { 243 if (mFinished) { 244 executor.execute(runnable); 245 return; 246 } 247 mSyncCompleteCallbacks.add(new Pair<>(executor, runnable)); 248 } 249 } 250 251 /** 252 * Mark the SurfaceSyncGroup as ready to complete. No more data can be added to this 253 * SurfaceSyncGroup. 254 * <p> 255 * Once the SurfaceSyncGroup is marked as ready, it will be able to complete once all child 256 * SurfaceSyncGroup have completed their sync. 257 */ markSyncReady()258 public void markSyncReady() { 259 if (DEBUG) { 260 Log.d(TAG, "markSyncReady " + mName); 261 } 262 if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { 263 Trace.instantForTrack(Trace.TRACE_TAG_VIEW, mTrackName, "markSyncReady"); 264 } 265 synchronized (mLock) { 266 if (mHasWMSync) { 267 try { 268 WindowManagerGlobal.getWindowManagerService().markSurfaceSyncGroupReady(mToken); 269 } catch (RemoteException e) { 270 } 271 } 272 mSyncReady = true; 273 checkIfSyncIsComplete(); 274 } 275 } 276 277 /** 278 * Add a SurfaceView to a SurfaceSyncGroup. This requires the caller to notify the start 279 * and finish drawing in order to sync since the client owns the rendering of the SurfaceView. 280 * 281 * @param surfaceView The SurfaceView to add to the sync. 282 * @param frameCallbackConsumer The callback that's invoked to allow the caller to notify 283 * SurfaceSyncGroup when the SurfaceView has started drawing. 284 * @return true if the SurfaceView was successfully added to the SyncGroup, false otherwise. 285 * @hide 286 */ 287 @UiThread add(SurfaceView surfaceView, Consumer<SurfaceViewFrameCallback> frameCallbackConsumer)288 public boolean add(SurfaceView surfaceView, 289 Consumer<SurfaceViewFrameCallback> frameCallbackConsumer) { 290 SurfaceSyncGroup surfaceSyncGroup = new SurfaceSyncGroup(surfaceView.getName()); 291 if (add(surfaceSyncGroup.mISurfaceSyncGroup, false /* parentSyncGroupMerge */, 292 null /* runnable */)) { 293 frameCallbackConsumer.accept(() -> surfaceView.syncNextFrame(transaction -> { 294 surfaceSyncGroup.addTransaction(transaction); 295 surfaceSyncGroup.markSyncReady(); 296 })); 297 return true; 298 } 299 return false; 300 } 301 302 /** 303 * Add an AttachedSurfaceControl to the SurfaceSyncGroup. The AttachedSurfaceControl will pause 304 * rendering to ensure the runnable can be invoked and that the sync picks up the frame that 305 * contains the changes. 306 * 307 * @param attachedSurfaceControl The AttachedSurfaceControl that will be add to this 308 * SurfaceSyncGroup. 309 * @param runnable This is run on the same thread that the call was made on, but 310 * after the rendering is paused and before continuing to render 311 * the next frame. This method will not return until the 312 * execution of the runnable completes. This can be used to make 313 * changes to the AttachedSurfaceControl, ensuring that the 314 * changes are included in the sync. 315 * @return true if the AttachedSurfaceControl was successfully added to the SurfaceSyncGroup, 316 * false otherwise. 317 */ 318 @UiThread add(@ullable AttachedSurfaceControl attachedSurfaceControl, @Nullable Runnable runnable)319 public boolean add(@Nullable AttachedSurfaceControl attachedSurfaceControl, 320 @Nullable Runnable runnable) { 321 if (attachedSurfaceControl == null) { 322 return false; 323 } 324 SurfaceSyncGroup surfaceSyncGroup = attachedSurfaceControl.getOrCreateSurfaceSyncGroup(); 325 if (surfaceSyncGroup == null) { 326 return false; 327 } 328 329 return add(surfaceSyncGroup, runnable); 330 } 331 332 /** 333 * Add a SurfaceControlViewHost.SurfacePackage to the SurfaceSyncGroup. This will 334 * get the SurfaceSyncGroup from the SurfacePackage, which will pause rendering for the 335 * SurfaceControlViewHost. The runnable will be invoked to allow the host to update the SCVH 336 * in a synchronized way. Finally, it will add the SCVH to the SurfaceSyncGroup and unpause 337 * rendering in the SCVH, allowing the changes to get picked up and included in the sync. 338 * 339 * @param surfacePackage The SurfacePackage that will be added to this SurfaceSyncGroup. 340 * @param runnable This is run on the same thread that the call was made on, but 341 * after the rendering is paused and before continuing to render 342 * the next frame. This method will not return until the 343 * execution of the runnable completes. This can be used to make 344 * changes to the SurfaceControlViewHost, ensuring that the 345 * changes are included in the sync. 346 * @return true if the SurfaceControlViewHost was successfully added to the current 347 * SurfaceSyncGroup, false otherwise. 348 */ add(@onNull SurfaceControlViewHost.SurfacePackage surfacePackage, @Nullable Runnable runnable)349 public boolean add(@NonNull SurfaceControlViewHost.SurfacePackage surfacePackage, 350 @Nullable Runnable runnable) { 351 ISurfaceSyncGroup surfaceSyncGroup; 352 try { 353 surfaceSyncGroup = surfacePackage.getRemoteInterface().getSurfaceSyncGroup(); 354 } catch (RemoteException e) { 355 Log.e(TAG, "Failed to add SurfaceControlViewHost to SurfaceSyncGroup"); 356 return false; 357 } 358 359 if (surfaceSyncGroup == null) { 360 Log.e(TAG, "Failed to add SurfaceControlViewHost to SurfaceSyncGroup. " 361 + "SCVH returned null SurfaceSyncGroup"); 362 return false; 363 } 364 return add(surfaceSyncGroup, false /* parentSyncGroupMerge */, runnable); 365 } 366 367 /** 368 * Add a SurfaceSyncGroup to the current SurfaceSyncGroup. 369 * 370 * @param surfaceSyncGroup The SurfaceSyncGroup that will be added to this SurfaceSyncGroup. 371 * @param runnable This is run on the same thread that the call was made on, This 372 * method will not return until the execution of the runnable 373 * completes. This can be used to make changes to the SurfaceSyncGroup, 374 * ensuring that the changes are included in the sync. 375 * @return true if the requested SurfaceSyncGroup was successfully added to the 376 * SurfaceSyncGroup, false otherwise. 377 * @hide 378 */ add(@onNull SurfaceSyncGroup surfaceSyncGroup, @Nullable Runnable runnable)379 public boolean add(@NonNull SurfaceSyncGroup surfaceSyncGroup, 380 @Nullable Runnable runnable) { 381 return add(surfaceSyncGroup.mISurfaceSyncGroup, false /* parentSyncGroupMerge */, 382 runnable); 383 } 384 385 /** 386 * Add a {@link ISurfaceSyncGroup} to a SurfaceSyncGroup. 387 * 388 * @param surfaceSyncGroup An ISyncableSurface that will be added to this SurfaceSyncGroup. 389 * @param parentSyncGroupMerge true if the ISurfaceSyncGroup is added because its child was 390 * added to a new SurfaceSyncGroup. That would require the code to 391 * call newParent.addToSync(oldParent). When this occurs, we need to 392 * reverse the merge order because the oldParent should always be 393 * considered older than any other SurfaceSyncGroups. 394 * @param runnable The Runnable that's invoked before adding the SurfaceSyncGroup 395 * @return true if the SyncGroup was successfully added to the current SyncGroup, false 396 * otherwise. 397 * @hide 398 */ add(ISurfaceSyncGroup surfaceSyncGroup, boolean parentSyncGroupMerge, @Nullable Runnable runnable)399 public boolean add(ISurfaceSyncGroup surfaceSyncGroup, boolean parentSyncGroupMerge, 400 @Nullable Runnable runnable) { 401 if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { 402 Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_VIEW, mTrackName, 403 "addToSync token=" + mToken.hashCode(), hashCode()); 404 } 405 synchronized (mLock) { 406 if (mSyncReady) { 407 Log.w(TAG, "Trying to add to sync when already marked as ready " + mName); 408 if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { 409 Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode()); 410 } 411 return false; 412 } 413 } 414 415 if (runnable != null) { 416 runnable.run(); 417 } 418 419 if (isLocalBinder(surfaceSyncGroup.asBinder())) { 420 boolean didAddLocalSync = addLocalSync(surfaceSyncGroup, parentSyncGroupMerge); 421 if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { 422 Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode()); 423 } 424 return didAddLocalSync; 425 } 426 427 synchronized (mLock) { 428 if (!mHasWMSync) { 429 // We need to add a signal into WMS since WMS will be creating a new parent 430 // SurfaceSyncGroup. When the parent SSG in WMS completes, only then do we 431 // notify the registered listeners that the entire SurfaceSyncGroup is complete. 432 // This is because the callers don't realize that when adding a different process 433 // to this SSG, it isn't actually adding to this SSG and really just creating a 434 // link in WMS. Because of this, the callers would expect the complete listeners 435 // to only be called when everything, including the other process's 436 // SurfaceSyncGroups, have completed. Only WMS has that info so we need to send the 437 // listener to WMS when we set up a server side sync. 438 mSurfaceSyncGroupCompletedListener = new ISurfaceSyncGroupCompletedListener.Stub() { 439 @Override 440 public void onSurfaceSyncGroupComplete() { 441 synchronized (mLock) { 442 invokeSyncCompleteCallbacks(); 443 } 444 } 445 }; 446 if (!addSyncToWm(mToken, false /* parentSyncGroupMerge */, 447 mSurfaceSyncGroupCompletedListener)) { 448 mSurfaceSyncGroupCompletedListener = null; 449 if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { 450 Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode()); 451 } 452 return false; 453 } 454 mHasWMSync = true; 455 } 456 } 457 458 try { 459 surfaceSyncGroup.onAddedToSyncGroup(mToken, parentSyncGroupMerge); 460 } catch (RemoteException e) { 461 if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { 462 Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode()); 463 } 464 return false; 465 } 466 467 if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { 468 Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode()); 469 } 470 return true; 471 } 472 473 /** 474 * Add a Transaction to this SurfaceSyncGroup. This allows the caller to provide other info that 475 * should be synced with the other transactions in this SurfaceSyncGroup. 476 * 477 * @param transaction The transaction to add to the SurfaceSyncGroup. 478 */ addTransaction(@onNull Transaction transaction)479 public void addTransaction(@NonNull Transaction transaction) { 480 synchronized (mLock) { 481 // If the caller tries to add a transaction to a completed SSG, just apply the 482 // transaction immediately since there's nothing to wait on. 483 if (mFinished) { 484 Log.w(TAG, "Adding transaction to a completed SurfaceSyncGroup(" + mName + "). " 485 + " Applying immediately"); 486 transaction.apply(); 487 } else { 488 mTransaction.merge(transaction); 489 } 490 } 491 } 492 493 /** 494 * Add a Runnable to be invoked when the SurfaceSyncGroup has been added to another 495 * SurfaceSyncGroup. This is useful to know when it's safe to proceed rendering. 496 * 497 * @hide 498 */ setAddedToSyncListener(Runnable addedToSyncListener)499 public void setAddedToSyncListener(Runnable addedToSyncListener) { 500 synchronized (mLock) { 501 mAddedToSyncListener = addedToSyncListener; 502 } 503 } 504 addSyncToWm(IBinder token, boolean parentSyncGroupMerge, @Nullable ISurfaceSyncGroupCompletedListener surfaceSyncGroupCompletedListener)505 private boolean addSyncToWm(IBinder token, boolean parentSyncGroupMerge, 506 @Nullable ISurfaceSyncGroupCompletedListener surfaceSyncGroupCompletedListener) { 507 try { 508 if (DEBUG) { 509 Log.d(TAG, "Attempting to add remote sync to " + mName 510 + ". Setting up Sync in WindowManager."); 511 } 512 if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { 513 Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_VIEW, mTrackName, 514 "addSyncToWm=" + token.hashCode(), hashCode()); 515 } 516 AddToSurfaceSyncGroupResult addToSyncGroupResult = new AddToSurfaceSyncGroupResult(); 517 if (!WindowManagerGlobal.getWindowManagerService().addToSurfaceSyncGroup(token, 518 parentSyncGroupMerge, surfaceSyncGroupCompletedListener, 519 addToSyncGroupResult)) { 520 if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { 521 Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode()); 522 } 523 return false; 524 } 525 526 setTransactionCallbackFromParent(addToSyncGroupResult.mParentSyncGroup, 527 addToSyncGroupResult.mTransactionReadyCallback); 528 } catch (RemoteException e) { 529 if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { 530 Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode()); 531 } 532 return false; 533 } 534 if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { 535 Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode()); 536 } 537 return true; 538 } 539 addLocalSync(ISurfaceSyncGroup childSyncToken, boolean parentSyncGroupMerge)540 private boolean addLocalSync(ISurfaceSyncGroup childSyncToken, boolean parentSyncGroupMerge) { 541 if (DEBUG) { 542 Log.d(TAG, "Adding local sync to " + mName); 543 } 544 545 SurfaceSyncGroup childSurfaceSyncGroup = getSurfaceSyncGroup(childSyncToken); 546 if (childSurfaceSyncGroup == null) { 547 Log.e(TAG, "Trying to add a local sync that's either not valid or not from the" 548 + " local process=" + childSyncToken); 549 return false; 550 } 551 552 if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { 553 Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_VIEW, mTrackName, 554 "addLocalSync=" + childSurfaceSyncGroup.mName, hashCode()); 555 } 556 ITransactionReadyCallback callback = 557 createTransactionReadyCallback(parentSyncGroupMerge); 558 559 if (callback == null) { 560 return false; 561 } 562 563 childSurfaceSyncGroup.setTransactionCallbackFromParent(mISurfaceSyncGroup, callback); 564 if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { 565 Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode()); 566 } 567 return true; 568 } 569 setTransactionCallbackFromParent(ISurfaceSyncGroup parentSyncGroup, ITransactionReadyCallback transactionReadyCallback)570 private void setTransactionCallbackFromParent(ISurfaceSyncGroup parentSyncGroup, 571 ITransactionReadyCallback transactionReadyCallback) { 572 if (DEBUG) { 573 Log.d(TAG, "setTransactionCallbackFromParent for child " + mName); 574 } 575 576 if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { 577 Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_VIEW, mTrackName, 578 "setTransactionCallbackFromParent " + mName + " callback=" 579 + transactionReadyCallback.hashCode(), hashCode()); 580 } 581 582 // Start the timeout when this SurfaceSyncGroup has been added to a parent SurfaceSyncGroup. 583 // This is because if the other SurfaceSyncGroup has bugs and doesn't complete, this SSG 584 // will get stuck. It's better to complete this SSG even if the parent SSG is broken. 585 addTimeout(); 586 587 boolean finished = false; 588 Runnable addedToSyncListener = null; 589 synchronized (mLock) { 590 if (mFinished) { 591 finished = true; 592 } else { 593 // If this SurfaceSyncGroup was already added to a different SurfaceSyncGroup, we 594 // need to combine everything. We can add the old SurfaceSyncGroup parent to the new 595 // parent so the new parent doesn't complete until the old parent does. 596 // Additionally, the old parent will not get the final transaction object and 597 // instead will send it to the new parent, ensuring that any other SurfaceSyncGroups 598 // from the original parent are also combined with the new parent SurfaceSyncGroup. 599 if (mParentSyncGroup != null && mParentSyncGroup != parentSyncGroup) { 600 if (DEBUG) { 601 Log.d(TAG, "Trying to add to " + parentSyncGroup 602 + " but already part of sync group " + mParentSyncGroup + " " 603 + mName); 604 } 605 try { 606 parentSyncGroup.addToSync(mParentSyncGroup, 607 true /* parentSyncGroupMerge */); 608 } catch (RemoteException e) { 609 } 610 } 611 612 if (DEBUG && mParentSyncGroup == parentSyncGroup) { 613 Log.d(TAG, "Added to parent that was already the parent"); 614 } 615 616 Consumer<Transaction> lastCallback = mTransactionReadyConsumer; 617 mParentSyncGroup = parentSyncGroup; 618 mTransactionReadyConsumer = (transaction) -> { 619 if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { 620 Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_VIEW, mTrackName, 621 "Invoke transactionReadyCallback=" 622 + transactionReadyCallback.hashCode(), hashCode()); 623 } 624 lastCallback.accept(null); 625 626 try { 627 transactionReadyCallback.onTransactionReady(transaction); 628 } catch (RemoteException e) { 629 transaction.apply(); 630 } 631 if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { 632 Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode()); 633 } 634 }; 635 addedToSyncListener = mAddedToSyncListener; 636 } 637 } 638 639 // Invoke the callback outside of the lock when the SurfaceSyncGroup being added was already 640 // complete. 641 if (finished) { 642 try { 643 transactionReadyCallback.onTransactionReady(null); 644 } catch (RemoteException e) { 645 } 646 } else if (addedToSyncListener != null) { 647 addedToSyncListener.run(); 648 } 649 if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { 650 Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode()); 651 } 652 } 653 654 /** 655 * @hide 656 */ getName()657 public String getName() { 658 return mName; 659 } 660 661 @GuardedBy("mLock") checkIfSyncIsComplete()662 private void checkIfSyncIsComplete() { 663 if (mFinished) { 664 if (DEBUG) { 665 Log.d(TAG, "SurfaceSyncGroup=" + mName + " is already complete"); 666 } 667 mTransaction.apply(); 668 return; 669 } 670 671 if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { 672 Trace.instantForTrack(Trace.TRACE_TAG_VIEW, mTrackName, 673 "checkIfSyncIsComplete mSyncReady=" + mSyncReady 674 + " mPendingSyncs=" + mPendingSyncs.size()); 675 } 676 677 if (!mSyncReady || !mPendingSyncs.isEmpty()) { 678 if (DEBUG) { 679 Log.d(TAG, "SurfaceSyncGroup=" + mName + " is not complete. mSyncReady=" 680 + mSyncReady + " mPendingSyncs=" + mPendingSyncs.size()); 681 } 682 return; 683 } 684 685 if (DEBUG) { 686 Log.d(TAG, "Successfully finished sync id=" + mName); 687 } 688 mTransactionReadyConsumer.accept(mTransaction); 689 mFinished = true; 690 if (mTimeoutAdded) { 691 mHandler.removeCallbacksAndMessages(this); 692 } 693 } 694 695 /** 696 * Create an {@link ITransactionReadyCallback} that the current SurfaceSyncGroup will wait on 697 * before completing. The caller must ensure that the 698 * {@link ITransactionReadyCallback#onTransactionReady(Transaction)} is called in order for this 699 * SurfaceSyncGroup to complete. 700 * 701 * @param parentSyncGroupMerge true if the ISurfaceSyncGroup is added because its child was 702 * added to a new SurfaceSyncGroup. That would require the code to 703 * call newParent.addToSync(oldParent). When this occurs, we need to 704 * reverse the merge order because the oldParent should always be 705 * considered older than any other SurfaceSyncGroups. 706 * @hide 707 */ createTransactionReadyCallback(boolean parentSyncGroupMerge)708 public ITransactionReadyCallback createTransactionReadyCallback(boolean parentSyncGroupMerge) { 709 if (DEBUG) { 710 Log.d(TAG, "createTransactionReadyCallback as part of " + mName); 711 } 712 ITransactionReadyCallback transactionReadyCallback = 713 new ITransactionReadyCallback.Stub() { 714 @Override 715 public void onTransactionReady(Transaction t) { 716 synchronized (mLock) { 717 if (t != null) { 718 t.sanitize(Binder.getCallingPid(), Binder.getCallingUid()); 719 // When an older parent sync group is added due to a child syncGroup 720 // getting added to multiple groups, we need to maintain merge order 721 // so the older parentSyncGroup transactions are overwritten by 722 // anything in the newer parentSyncGroup. 723 if (parentSyncGroupMerge) { 724 t.merge(mTransaction); 725 } 726 mTransaction.merge(t); 727 } 728 mPendingSyncs.remove(this); 729 if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { 730 Trace.instantForTrack(Trace.TRACE_TAG_VIEW, mTrackName, 731 "onTransactionReady callback=" + hashCode()); 732 } 733 checkIfSyncIsComplete(); 734 } 735 } 736 }; 737 738 synchronized (mLock) { 739 if (mSyncReady) { 740 Log.e(TAG, "Sync " + mName 741 + " was already marked as ready. No more SurfaceSyncGroups can be added."); 742 return null; 743 } 744 mPendingSyncs.add(transactionReadyCallback); 745 if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { 746 Trace.instantForTrack(Trace.TRACE_TAG_VIEW, mTrackName, 747 "createTransactionReadyCallback mPendingSyncs=" 748 + mPendingSyncs.size() + " transactionReady=" 749 + transactionReadyCallback.hashCode()); 750 } 751 } 752 753 // Start the timeout when another SSG has been added to this SurfaceSyncGroup. This is 754 // because if the other SurfaceSyncGroup has bugs and doesn't complete, it will affect this 755 // SSGs. So it's better to just add a timeout in case the other SSG doesn't invoke the 756 // callback and complete this SSG. 757 addTimeout(); 758 759 return transactionReadyCallback; 760 } 761 762 private class ISurfaceSyncGroupImpl extends ISurfaceSyncGroup.Stub { 763 @Override onAddedToSyncGroup(IBinder parentSyncGroupToken, boolean parentSyncGroupMerge)764 public boolean onAddedToSyncGroup(IBinder parentSyncGroupToken, 765 boolean parentSyncGroupMerge) { 766 if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { 767 Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_VIEW, mTrackName, 768 "onAddedToSyncGroup token=" + parentSyncGroupToken.hashCode(), hashCode()); 769 } 770 boolean didAdd = addSyncToWm(parentSyncGroupToken, parentSyncGroupMerge, null); 771 if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { 772 Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode()); 773 } 774 return didAdd; 775 } 776 777 @Override addToSync(ISurfaceSyncGroup surfaceSyncGroup, boolean parentSyncGroupMerge)778 public boolean addToSync(ISurfaceSyncGroup surfaceSyncGroup, boolean parentSyncGroupMerge) { 779 return SurfaceSyncGroup.this.add(surfaceSyncGroup, parentSyncGroupMerge, 780 null /* runnable */); 781 } 782 getSurfaceSyncGroup()783 SurfaceSyncGroup getSurfaceSyncGroup() { 784 return SurfaceSyncGroup.this; 785 } 786 } 787 788 /** 789 * @hide 790 */ toggleTimeout(boolean enable)791 public void toggleTimeout(boolean enable) { 792 synchronized (mLock) { 793 mTimeoutDisabled = !enable; 794 if (mTimeoutAdded && !enable) { 795 mHandler.removeCallbacksAndMessages(this); 796 mTimeoutAdded = false; 797 } else if (!mTimeoutAdded && enable) { 798 addTimeout(); 799 } 800 } 801 } 802 addTimeout()803 private void addTimeout() { 804 Looper looper = null; 805 synchronized (sHandlerThreadLock) { 806 if (sHandlerThread == null) { 807 sHandlerThread = new HandlerThread("SurfaceSyncGroupTimer"); 808 sHandlerThread.start(); 809 } 810 811 looper = sHandlerThread.getLooper(); 812 } 813 814 synchronized (mLock) { 815 if (mTimeoutAdded || mTimeoutDisabled || looper == null) { 816 // We only need one timeout for the entire SurfaceSyncGroup since we just want to 817 // ensure it doesn't stay stuck forever. 818 return; 819 } 820 821 if (mHandler == null) { 822 mHandler = new Handler(looper); 823 } 824 825 mTimeoutAdded = true; 826 } 827 828 Runnable runnable = () -> { 829 Log.e(TAG, "Failed to receive transaction ready in " + TRANSACTION_READY_TIMEOUT 830 + "ms. Marking SurfaceSyncGroup(" + mName + ") as ready"); 831 // Clear out any pending syncs in case the other syncs can't complete or timeout due to 832 // a crash. 833 synchronized (mLock) { 834 mPendingSyncs.clear(); 835 } 836 markSyncReady(); 837 }; 838 mHandler.postDelayed(runnable, this, TRANSACTION_READY_TIMEOUT); 839 } 840 841 /** 842 * A frame callback that is used to synchronize SurfaceViews. The owner of the SurfaceView must 843 * implement onFrameStarted when trying to sync the SurfaceView. This is to ensure the sync 844 * knows when the frame is ready to add to the sync. 845 * 846 * @hide 847 */ 848 public interface SurfaceViewFrameCallback { 849 /** 850 * Called when the SurfaceView is going to render a frame 851 */ onFrameStarted()852 void onFrameStarted(); 853 } 854 } 855