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