855 lines
35 KiB
Java
855 lines
35 KiB
Java
/*
|
|
* Copyright (C) 2022 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package android.window;
|
|
|
|
import android.annotation.NonNull;
|
|
import android.annotation.Nullable;
|
|
import android.annotation.UiThread;
|
|
import android.os.Binder;
|
|
import android.os.BinderProxy;
|
|
import android.os.Build;
|
|
import android.os.Debug;
|
|
import android.os.Handler;
|
|
import android.os.HandlerThread;
|
|
import android.os.IBinder;
|
|
import android.os.Looper;
|
|
import android.os.RemoteException;
|
|
import android.os.Trace;
|
|
import android.util.ArraySet;
|
|
import android.util.Log;
|
|
import android.util.Pair;
|
|
import android.view.AttachedSurfaceControl;
|
|
import android.view.SurfaceControl.Transaction;
|
|
import android.view.SurfaceControlViewHost;
|
|
import android.view.SurfaceView;
|
|
import android.view.WindowManagerGlobal;
|
|
|
|
import com.android.internal.annotations.GuardedBy;
|
|
import com.android.internal.annotations.VisibleForTesting;
|
|
|
|
import java.util.concurrent.Executor;
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
import java.util.function.Consumer;
|
|
import java.util.function.Supplier;
|
|
|
|
/**
|
|
* A way for data to be gathered so multiple surfaces can be synced. This is intended to be
|
|
* used with AttachedSurfaceControl, SurfaceView, and SurfaceControlViewHost. This allows different
|
|
* parts of the system to synchronize different surfaces themselves without having to manage timing
|
|
* of different rendering threads.
|
|
* This will also allow synchronization of surfaces across multiple processes. The caller can add
|
|
* SurfaceControlViewHosts from another process to the SurfaceSyncGroup in a different process
|
|
* and this clas will ensure all the surfaces are ready before applying everything together.
|
|
* see the <a href="https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/window/SurfaceSyncGroup.md">SurfaceSyncGroup documentation</a>
|
|
* </p>
|
|
*/
|
|
public final class SurfaceSyncGroup {
|
|
private static final String TAG = "SurfaceSyncGroup";
|
|
private static final boolean DEBUG = false;
|
|
|
|
private static final int MAX_COUNT = 100;
|
|
|
|
private static final AtomicInteger sCounter = new AtomicInteger(0);
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
@VisibleForTesting
|
|
public static final int TRANSACTION_READY_TIMEOUT = 1000 * Build.HW_TIMEOUT_MULTIPLIER;
|
|
|
|
private static Supplier<Transaction> sTransactionFactory = Transaction::new;
|
|
|
|
/**
|
|
* Class that collects the {@link SurfaceSyncGroup}s and notifies when all the surfaces have
|
|
* a frame ready.
|
|
*/
|
|
private final Object mLock = new Object();
|
|
|
|
private final String mName;
|
|
|
|
@GuardedBy("mLock")
|
|
private final ArraySet<ITransactionReadyCallback> mPendingSyncs = new ArraySet<>();
|
|
@GuardedBy("mLock")
|
|
private final Transaction mTransaction = sTransactionFactory.get();
|
|
@GuardedBy("mLock")
|
|
private boolean mSyncReady;
|
|
|
|
@GuardedBy("mLock")
|
|
private boolean mFinished;
|
|
|
|
@GuardedBy("mLock")
|
|
private Consumer<Transaction> mTransactionReadyConsumer;
|
|
|
|
@GuardedBy("mLock")
|
|
private ISurfaceSyncGroup mParentSyncGroup;
|
|
|
|
@GuardedBy("mLock")
|
|
private final ArraySet<Pair<Executor, Runnable>> mSyncCompleteCallbacks = new ArraySet<>();
|
|
|
|
@GuardedBy("mLock")
|
|
private boolean mHasWMSync;
|
|
|
|
@GuardedBy("mLock")
|
|
private ISurfaceSyncGroupCompletedListener mSurfaceSyncGroupCompletedListener;
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
public final ISurfaceSyncGroup mISurfaceSyncGroup = new ISurfaceSyncGroupImpl();
|
|
|
|
@GuardedBy("mLock")
|
|
private Runnable mAddedToSyncListener;
|
|
|
|
/**
|
|
* Token to identify this SurfaceSyncGroup. This is used to register the SurfaceSyncGroup in
|
|
* WindowManager. This token is also sent to other processes' SurfaceSyncGroup that want to be
|
|
* included in this SurfaceSyncGroup.
|
|
*/
|
|
private final Binder mToken = new Binder();
|
|
|
|
private static final Object sHandlerThreadLock = new Object();
|
|
@GuardedBy("sHandlerThreadLock")
|
|
private static HandlerThread sHandlerThread;
|
|
private Handler mHandler;
|
|
|
|
@GuardedBy("mLock")
|
|
private boolean mTimeoutAdded;
|
|
|
|
/**
|
|
* Disable the timeout for this SSG so it will never be set until there's an explicit call to
|
|
* add a timeout.
|
|
*/
|
|
@GuardedBy("mLock")
|
|
private boolean mTimeoutDisabled;
|
|
|
|
private final String mTrackName;
|
|
|
|
private static boolean isLocalBinder(IBinder binder) {
|
|
return !(binder instanceof BinderProxy);
|
|
}
|
|
|
|
private static SurfaceSyncGroup getSurfaceSyncGroup(ISurfaceSyncGroup iSurfaceSyncGroup) {
|
|
if (iSurfaceSyncGroup instanceof ISurfaceSyncGroupImpl) {
|
|
return ((ISurfaceSyncGroupImpl) iSurfaceSyncGroup).getSurfaceSyncGroup();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
public static void setTransactionFactory(Supplier<Transaction> transactionFactory) {
|
|
sTransactionFactory = transactionFactory;
|
|
}
|
|
|
|
/**
|
|
* Starts a sync and will automatically apply the final, merged transaction.
|
|
*
|
|
* @param name Used for identifying and debugging.
|
|
*/
|
|
public SurfaceSyncGroup(@NonNull String name) {
|
|
this(name, transaction -> {
|
|
if (transaction != null) {
|
|
if (DEBUG) {
|
|
Log.d(TAG, "Applying transaction " + transaction);
|
|
}
|
|
transaction.apply();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Creates a sync.
|
|
*
|
|
* @param name Used for identifying and debugging.
|
|
* @param transactionReadyConsumer The complete callback that contains the syncId and
|
|
* transaction with all the sync data merged. The Transaction
|
|
* passed back can be null.
|
|
* <p>
|
|
* NOTE: Only should be used by ViewRootImpl
|
|
* @hide
|
|
*/
|
|
public SurfaceSyncGroup(String name, Consumer<Transaction> transactionReadyConsumer) {
|
|
// sCounter is a way to give the SurfaceSyncGroup a unique name even if the name passed in
|
|
// is not.
|
|
// Avoid letting the count get too big so just reset to 0. It's unlikely that we'll have
|
|
// more than MAX_COUNT active syncs that have overlapping names
|
|
if (sCounter.get() >= MAX_COUNT) {
|
|
sCounter.set(0);
|
|
}
|
|
|
|
mName = name + "#" + sCounter.getAndIncrement();
|
|
mTrackName = "SurfaceSyncGroup " + name;
|
|
|
|
mTransactionReadyConsumer = (transaction) -> {
|
|
if (DEBUG && transaction != null) {
|
|
Log.d(TAG, "Sending non null transaction " + transaction + " to callback for "
|
|
+ mName);
|
|
}
|
|
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
|
|
Trace.instantForTrack(Trace.TRACE_TAG_VIEW, mTrackName,
|
|
"Final TransactionCallback with " + transaction);
|
|
}
|
|
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode());
|
|
transactionReadyConsumer.accept(transaction);
|
|
synchronized (mLock) {
|
|
// If there's a registered listener with WMS, that means we aren't actually complete
|
|
// until WMS notifies us that the parent has completed.
|
|
if (mSurfaceSyncGroupCompletedListener == null) {
|
|
invokeSyncCompleteCallbacks();
|
|
}
|
|
}
|
|
};
|
|
|
|
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
|
|
Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_VIEW, mTrackName, mName, hashCode());
|
|
}
|
|
|
|
if (DEBUG) {
|
|
Log.d(TAG, "setupSync " + mName + " " + Debug.getCallers(2));
|
|
}
|
|
}
|
|
|
|
@GuardedBy("mLock")
|
|
private void invokeSyncCompleteCallbacks() {
|
|
mSyncCompleteCallbacks.forEach(
|
|
executorRunnablePair -> executorRunnablePair.first.execute(
|
|
executorRunnablePair.second));
|
|
}
|
|
|
|
/**
|
|
* Add a {@link Runnable} to be executed when the sync completes.
|
|
*
|
|
* @param executor The Executor to invoke the Runnable on
|
|
* @param runnable The Runnable to get called
|
|
* @hide
|
|
*/
|
|
public void addSyncCompleteCallback(Executor executor, Runnable runnable) {
|
|
synchronized (mLock) {
|
|
if (mFinished) {
|
|
executor.execute(runnable);
|
|
return;
|
|
}
|
|
mSyncCompleteCallbacks.add(new Pair<>(executor, runnable));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Mark the SurfaceSyncGroup as ready to complete. No more data can be added to this
|
|
* SurfaceSyncGroup.
|
|
* <p>
|
|
* Once the SurfaceSyncGroup is marked as ready, it will be able to complete once all child
|
|
* SurfaceSyncGroup have completed their sync.
|
|
*/
|
|
public void markSyncReady() {
|
|
if (DEBUG) {
|
|
Log.d(TAG, "markSyncReady " + mName);
|
|
}
|
|
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
|
|
Trace.instantForTrack(Trace.TRACE_TAG_VIEW, mTrackName, "markSyncReady");
|
|
}
|
|
synchronized (mLock) {
|
|
if (mHasWMSync) {
|
|
try {
|
|
WindowManagerGlobal.getWindowManagerService().markSurfaceSyncGroupReady(mToken);
|
|
} catch (RemoteException e) {
|
|
}
|
|
}
|
|
mSyncReady = true;
|
|
checkIfSyncIsComplete();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add a SurfaceView to a SurfaceSyncGroup. This requires the caller to notify the start
|
|
* and finish drawing in order to sync since the client owns the rendering of the SurfaceView.
|
|
*
|
|
* @param surfaceView The SurfaceView to add to the sync.
|
|
* @param frameCallbackConsumer The callback that's invoked to allow the caller to notify
|
|
* SurfaceSyncGroup when the SurfaceView has started drawing.
|
|
* @return true if the SurfaceView was successfully added to the SyncGroup, false otherwise.
|
|
* @hide
|
|
*/
|
|
@UiThread
|
|
public boolean add(SurfaceView surfaceView,
|
|
Consumer<SurfaceViewFrameCallback> frameCallbackConsumer) {
|
|
SurfaceSyncGroup surfaceSyncGroup = new SurfaceSyncGroup(surfaceView.getName());
|
|
if (add(surfaceSyncGroup.mISurfaceSyncGroup, false /* parentSyncGroupMerge */,
|
|
null /* runnable */)) {
|
|
frameCallbackConsumer.accept(() -> surfaceView.syncNextFrame(transaction -> {
|
|
surfaceSyncGroup.addTransaction(transaction);
|
|
surfaceSyncGroup.markSyncReady();
|
|
}));
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Add an AttachedSurfaceControl to the SurfaceSyncGroup. The AttachedSurfaceControl will pause
|
|
* rendering to ensure the runnable can be invoked and that the sync picks up the frame that
|
|
* contains the changes.
|
|
*
|
|
* @param attachedSurfaceControl The AttachedSurfaceControl that will be add to this
|
|
* SurfaceSyncGroup.
|
|
* @param runnable This is run on the same thread that the call was made on, but
|
|
* after the rendering is paused and before continuing to render
|
|
* the next frame. This method will not return until the
|
|
* execution of the runnable completes. This can be used to make
|
|
* changes to the AttachedSurfaceControl, ensuring that the
|
|
* changes are included in the sync.
|
|
* @return true if the AttachedSurfaceControl was successfully added to the SurfaceSyncGroup,
|
|
* false otherwise.
|
|
*/
|
|
@UiThread
|
|
public boolean add(@Nullable AttachedSurfaceControl attachedSurfaceControl,
|
|
@Nullable Runnable runnable) {
|
|
if (attachedSurfaceControl == null) {
|
|
return false;
|
|
}
|
|
SurfaceSyncGroup surfaceSyncGroup = attachedSurfaceControl.getOrCreateSurfaceSyncGroup();
|
|
if (surfaceSyncGroup == null) {
|
|
return false;
|
|
}
|
|
|
|
return add(surfaceSyncGroup, runnable);
|
|
}
|
|
|
|
/**
|
|
* Add a SurfaceControlViewHost.SurfacePackage to the SurfaceSyncGroup. This will
|
|
* get the SurfaceSyncGroup from the SurfacePackage, which will pause rendering for the
|
|
* SurfaceControlViewHost. The runnable will be invoked to allow the host to update the SCVH
|
|
* in a synchronized way. Finally, it will add the SCVH to the SurfaceSyncGroup and unpause
|
|
* rendering in the SCVH, allowing the changes to get picked up and included in the sync.
|
|
*
|
|
* @param surfacePackage The SurfacePackage that will be added to this SurfaceSyncGroup.
|
|
* @param runnable This is run on the same thread that the call was made on, but
|
|
* after the rendering is paused and before continuing to render
|
|
* the next frame. This method will not return until the
|
|
* execution of the runnable completes. This can be used to make
|
|
* changes to the SurfaceControlViewHost, ensuring that the
|
|
* changes are included in the sync.
|
|
* @return true if the SurfaceControlViewHost was successfully added to the current
|
|
* SurfaceSyncGroup, false otherwise.
|
|
*/
|
|
public boolean add(@NonNull SurfaceControlViewHost.SurfacePackage surfacePackage,
|
|
@Nullable Runnable runnable) {
|
|
ISurfaceSyncGroup surfaceSyncGroup;
|
|
try {
|
|
surfaceSyncGroup = surfacePackage.getRemoteInterface().getSurfaceSyncGroup();
|
|
} catch (RemoteException e) {
|
|
Log.e(TAG, "Failed to add SurfaceControlViewHost to SurfaceSyncGroup");
|
|
return false;
|
|
}
|
|
|
|
if (surfaceSyncGroup == null) {
|
|
Log.e(TAG, "Failed to add SurfaceControlViewHost to SurfaceSyncGroup. "
|
|
+ "SCVH returned null SurfaceSyncGroup");
|
|
return false;
|
|
}
|
|
return add(surfaceSyncGroup, false /* parentSyncGroupMerge */, runnable);
|
|
}
|
|
|
|
/**
|
|
* Add a SurfaceSyncGroup to the current SurfaceSyncGroup.
|
|
*
|
|
* @param surfaceSyncGroup The SurfaceSyncGroup that will be added to this SurfaceSyncGroup.
|
|
* @param runnable This is run on the same thread that the call was made on, This
|
|
* method will not return until the execution of the runnable
|
|
* completes. This can be used to make changes to the SurfaceSyncGroup,
|
|
* ensuring that the changes are included in the sync.
|
|
* @return true if the requested SurfaceSyncGroup was successfully added to the
|
|
* SurfaceSyncGroup, false otherwise.
|
|
* @hide
|
|
*/
|
|
public boolean add(@NonNull SurfaceSyncGroup surfaceSyncGroup,
|
|
@Nullable Runnable runnable) {
|
|
return add(surfaceSyncGroup.mISurfaceSyncGroup, false /* parentSyncGroupMerge */,
|
|
runnable);
|
|
}
|
|
|
|
/**
|
|
* Add a {@link ISurfaceSyncGroup} to a SurfaceSyncGroup.
|
|
*
|
|
* @param surfaceSyncGroup An ISyncableSurface that will be added to this SurfaceSyncGroup.
|
|
* @param parentSyncGroupMerge true if the ISurfaceSyncGroup is added because its child was
|
|
* added to a new SurfaceSyncGroup. That would require the code to
|
|
* call newParent.addToSync(oldParent). When this occurs, we need to
|
|
* reverse the merge order because the oldParent should always be
|
|
* considered older than any other SurfaceSyncGroups.
|
|
* @param runnable The Runnable that's invoked before adding the SurfaceSyncGroup
|
|
* @return true if the SyncGroup was successfully added to the current SyncGroup, false
|
|
* otherwise.
|
|
* @hide
|
|
*/
|
|
public boolean add(ISurfaceSyncGroup surfaceSyncGroup, boolean parentSyncGroupMerge,
|
|
@Nullable Runnable runnable) {
|
|
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
|
|
Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_VIEW, mTrackName,
|
|
"addToSync token=" + mToken.hashCode(), hashCode());
|
|
}
|
|
synchronized (mLock) {
|
|
if (mSyncReady) {
|
|
Log.w(TAG, "Trying to add to sync when already marked as ready " + mName);
|
|
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
|
|
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode());
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (runnable != null) {
|
|
runnable.run();
|
|
}
|
|
|
|
if (isLocalBinder(surfaceSyncGroup.asBinder())) {
|
|
boolean didAddLocalSync = addLocalSync(surfaceSyncGroup, parentSyncGroupMerge);
|
|
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
|
|
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode());
|
|
}
|
|
return didAddLocalSync;
|
|
}
|
|
|
|
synchronized (mLock) {
|
|
if (!mHasWMSync) {
|
|
// We need to add a signal into WMS since WMS will be creating a new parent
|
|
// SurfaceSyncGroup. When the parent SSG in WMS completes, only then do we
|
|
// notify the registered listeners that the entire SurfaceSyncGroup is complete.
|
|
// This is because the callers don't realize that when adding a different process
|
|
// to this SSG, it isn't actually adding to this SSG and really just creating a
|
|
// link in WMS. Because of this, the callers would expect the complete listeners
|
|
// to only be called when everything, including the other process's
|
|
// SurfaceSyncGroups, have completed. Only WMS has that info so we need to send the
|
|
// listener to WMS when we set up a server side sync.
|
|
mSurfaceSyncGroupCompletedListener = new ISurfaceSyncGroupCompletedListener.Stub() {
|
|
@Override
|
|
public void onSurfaceSyncGroupComplete() {
|
|
synchronized (mLock) {
|
|
invokeSyncCompleteCallbacks();
|
|
}
|
|
}
|
|
};
|
|
if (!addSyncToWm(mToken, false /* parentSyncGroupMerge */,
|
|
mSurfaceSyncGroupCompletedListener)) {
|
|
mSurfaceSyncGroupCompletedListener = null;
|
|
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
|
|
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode());
|
|
}
|
|
return false;
|
|
}
|
|
mHasWMSync = true;
|
|
}
|
|
}
|
|
|
|
try {
|
|
surfaceSyncGroup.onAddedToSyncGroup(mToken, parentSyncGroupMerge);
|
|
} catch (RemoteException e) {
|
|
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
|
|
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode());
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
|
|
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Add a Transaction to this SurfaceSyncGroup. This allows the caller to provide other info that
|
|
* should be synced with the other transactions in this SurfaceSyncGroup.
|
|
*
|
|
* @param transaction The transaction to add to the SurfaceSyncGroup.
|
|
*/
|
|
public void addTransaction(@NonNull Transaction transaction) {
|
|
synchronized (mLock) {
|
|
// If the caller tries to add a transaction to a completed SSG, just apply the
|
|
// transaction immediately since there's nothing to wait on.
|
|
if (mFinished) {
|
|
Log.w(TAG, "Adding transaction to a completed SurfaceSyncGroup(" + mName + "). "
|
|
+ " Applying immediately");
|
|
transaction.apply();
|
|
} else {
|
|
mTransaction.merge(transaction);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add a Runnable to be invoked when the SurfaceSyncGroup has been added to another
|
|
* SurfaceSyncGroup. This is useful to know when it's safe to proceed rendering.
|
|
*
|
|
* @hide
|
|
*/
|
|
public void setAddedToSyncListener(Runnable addedToSyncListener) {
|
|
synchronized (mLock) {
|
|
mAddedToSyncListener = addedToSyncListener;
|
|
}
|
|
}
|
|
|
|
private boolean addSyncToWm(IBinder token, boolean parentSyncGroupMerge,
|
|
@Nullable ISurfaceSyncGroupCompletedListener surfaceSyncGroupCompletedListener) {
|
|
try {
|
|
if (DEBUG) {
|
|
Log.d(TAG, "Attempting to add remote sync to " + mName
|
|
+ ". Setting up Sync in WindowManager.");
|
|
}
|
|
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
|
|
Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_VIEW, mTrackName,
|
|
"addSyncToWm=" + token.hashCode(), hashCode());
|
|
}
|
|
AddToSurfaceSyncGroupResult addToSyncGroupResult = new AddToSurfaceSyncGroupResult();
|
|
if (!WindowManagerGlobal.getWindowManagerService().addToSurfaceSyncGroup(token,
|
|
parentSyncGroupMerge, surfaceSyncGroupCompletedListener,
|
|
addToSyncGroupResult)) {
|
|
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
|
|
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode());
|
|
}
|
|
return false;
|
|
}
|
|
|
|
setTransactionCallbackFromParent(addToSyncGroupResult.mParentSyncGroup,
|
|
addToSyncGroupResult.mTransactionReadyCallback);
|
|
} catch (RemoteException e) {
|
|
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
|
|
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode());
|
|
}
|
|
return false;
|
|
}
|
|
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
|
|
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private boolean addLocalSync(ISurfaceSyncGroup childSyncToken, boolean parentSyncGroupMerge) {
|
|
if (DEBUG) {
|
|
Log.d(TAG, "Adding local sync to " + mName);
|
|
}
|
|
|
|
SurfaceSyncGroup childSurfaceSyncGroup = getSurfaceSyncGroup(childSyncToken);
|
|
if (childSurfaceSyncGroup == null) {
|
|
Log.e(TAG, "Trying to add a local sync that's either not valid or not from the"
|
|
+ " local process=" + childSyncToken);
|
|
return false;
|
|
}
|
|
|
|
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
|
|
Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_VIEW, mTrackName,
|
|
"addLocalSync=" + childSurfaceSyncGroup.mName, hashCode());
|
|
}
|
|
ITransactionReadyCallback callback =
|
|
createTransactionReadyCallback(parentSyncGroupMerge);
|
|
|
|
if (callback == null) {
|
|
return false;
|
|
}
|
|
|
|
childSurfaceSyncGroup.setTransactionCallbackFromParent(mISurfaceSyncGroup, callback);
|
|
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
|
|
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private void setTransactionCallbackFromParent(ISurfaceSyncGroup parentSyncGroup,
|
|
ITransactionReadyCallback transactionReadyCallback) {
|
|
if (DEBUG) {
|
|
Log.d(TAG, "setTransactionCallbackFromParent for child " + mName);
|
|
}
|
|
|
|
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
|
|
Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_VIEW, mTrackName,
|
|
"setTransactionCallbackFromParent " + mName + " callback="
|
|
+ transactionReadyCallback.hashCode(), hashCode());
|
|
}
|
|
|
|
// Start the timeout when this SurfaceSyncGroup has been added to a parent SurfaceSyncGroup.
|
|
// This is because if the other SurfaceSyncGroup has bugs and doesn't complete, this SSG
|
|
// will get stuck. It's better to complete this SSG even if the parent SSG is broken.
|
|
addTimeout();
|
|
|
|
boolean finished = false;
|
|
Runnable addedToSyncListener = null;
|
|
synchronized (mLock) {
|
|
if (mFinished) {
|
|
finished = true;
|
|
} else {
|
|
// If this SurfaceSyncGroup was already added to a different SurfaceSyncGroup, we
|
|
// need to combine everything. We can add the old SurfaceSyncGroup parent to the new
|
|
// parent so the new parent doesn't complete until the old parent does.
|
|
// Additionally, the old parent will not get the final transaction object and
|
|
// instead will send it to the new parent, ensuring that any other SurfaceSyncGroups
|
|
// from the original parent are also combined with the new parent SurfaceSyncGroup.
|
|
if (mParentSyncGroup != null && mParentSyncGroup != parentSyncGroup) {
|
|
if (DEBUG) {
|
|
Log.d(TAG, "Trying to add to " + parentSyncGroup
|
|
+ " but already part of sync group " + mParentSyncGroup + " "
|
|
+ mName);
|
|
}
|
|
try {
|
|
parentSyncGroup.addToSync(mParentSyncGroup,
|
|
true /* parentSyncGroupMerge */);
|
|
} catch (RemoteException e) {
|
|
}
|
|
}
|
|
|
|
if (DEBUG && mParentSyncGroup == parentSyncGroup) {
|
|
Log.d(TAG, "Added to parent that was already the parent");
|
|
}
|
|
|
|
Consumer<Transaction> lastCallback = mTransactionReadyConsumer;
|
|
mParentSyncGroup = parentSyncGroup;
|
|
mTransactionReadyConsumer = (transaction) -> {
|
|
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
|
|
Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_VIEW, mTrackName,
|
|
"Invoke transactionReadyCallback="
|
|
+ transactionReadyCallback.hashCode(), hashCode());
|
|
}
|
|
lastCallback.accept(null);
|
|
|
|
try {
|
|
transactionReadyCallback.onTransactionReady(transaction);
|
|
} catch (RemoteException e) {
|
|
transaction.apply();
|
|
}
|
|
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
|
|
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode());
|
|
}
|
|
};
|
|
addedToSyncListener = mAddedToSyncListener;
|
|
}
|
|
}
|
|
|
|
// Invoke the callback outside of the lock when the SurfaceSyncGroup being added was already
|
|
// complete.
|
|
if (finished) {
|
|
try {
|
|
transactionReadyCallback.onTransactionReady(null);
|
|
} catch (RemoteException e) {
|
|
}
|
|
} else if (addedToSyncListener != null) {
|
|
addedToSyncListener.run();
|
|
}
|
|
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
|
|
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
public String getName() {
|
|
return mName;
|
|
}
|
|
|
|
@GuardedBy("mLock")
|
|
private void checkIfSyncIsComplete() {
|
|
if (mFinished) {
|
|
if (DEBUG) {
|
|
Log.d(TAG, "SurfaceSyncGroup=" + mName + " is already complete");
|
|
}
|
|
mTransaction.apply();
|
|
return;
|
|
}
|
|
|
|
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
|
|
Trace.instantForTrack(Trace.TRACE_TAG_VIEW, mTrackName,
|
|
"checkIfSyncIsComplete mSyncReady=" + mSyncReady
|
|
+ " mPendingSyncs=" + mPendingSyncs.size());
|
|
}
|
|
|
|
if (!mSyncReady || !mPendingSyncs.isEmpty()) {
|
|
if (DEBUG) {
|
|
Log.d(TAG, "SurfaceSyncGroup=" + mName + " is not complete. mSyncReady="
|
|
+ mSyncReady + " mPendingSyncs=" + mPendingSyncs.size());
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (DEBUG) {
|
|
Log.d(TAG, "Successfully finished sync id=" + mName);
|
|
}
|
|
mTransactionReadyConsumer.accept(mTransaction);
|
|
mFinished = true;
|
|
if (mTimeoutAdded) {
|
|
mHandler.removeCallbacksAndMessages(this);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create an {@link ITransactionReadyCallback} that the current SurfaceSyncGroup will wait on
|
|
* before completing. The caller must ensure that the
|
|
* {@link ITransactionReadyCallback#onTransactionReady(Transaction)} is called in order for this
|
|
* SurfaceSyncGroup to complete.
|
|
*
|
|
* @param parentSyncGroupMerge true if the ISurfaceSyncGroup is added because its child was
|
|
* added to a new SurfaceSyncGroup. That would require the code to
|
|
* call newParent.addToSync(oldParent). When this occurs, we need to
|
|
* reverse the merge order because the oldParent should always be
|
|
* considered older than any other SurfaceSyncGroups.
|
|
* @hide
|
|
*/
|
|
public ITransactionReadyCallback createTransactionReadyCallback(boolean parentSyncGroupMerge) {
|
|
if (DEBUG) {
|
|
Log.d(TAG, "createTransactionReadyCallback as part of " + mName);
|
|
}
|
|
ITransactionReadyCallback transactionReadyCallback =
|
|
new ITransactionReadyCallback.Stub() {
|
|
@Override
|
|
public void onTransactionReady(Transaction t) {
|
|
synchronized (mLock) {
|
|
if (t != null) {
|
|
t.sanitize(Binder.getCallingPid(), Binder.getCallingUid());
|
|
// When an older parent sync group is added due to a child syncGroup
|
|
// getting added to multiple groups, we need to maintain merge order
|
|
// so the older parentSyncGroup transactions are overwritten by
|
|
// anything in the newer parentSyncGroup.
|
|
if (parentSyncGroupMerge) {
|
|
t.merge(mTransaction);
|
|
}
|
|
mTransaction.merge(t);
|
|
}
|
|
mPendingSyncs.remove(this);
|
|
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
|
|
Trace.instantForTrack(Trace.TRACE_TAG_VIEW, mTrackName,
|
|
"onTransactionReady callback=" + hashCode());
|
|
}
|
|
checkIfSyncIsComplete();
|
|
}
|
|
}
|
|
};
|
|
|
|
synchronized (mLock) {
|
|
if (mSyncReady) {
|
|
Log.e(TAG, "Sync " + mName
|
|
+ " was already marked as ready. No more SurfaceSyncGroups can be added.");
|
|
return null;
|
|
}
|
|
mPendingSyncs.add(transactionReadyCallback);
|
|
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
|
|
Trace.instantForTrack(Trace.TRACE_TAG_VIEW, mTrackName,
|
|
"createTransactionReadyCallback mPendingSyncs="
|
|
+ mPendingSyncs.size() + " transactionReady="
|
|
+ transactionReadyCallback.hashCode());
|
|
}
|
|
}
|
|
|
|
// Start the timeout when another SSG has been added to this SurfaceSyncGroup. This is
|
|
// because if the other SurfaceSyncGroup has bugs and doesn't complete, it will affect this
|
|
// SSGs. So it's better to just add a timeout in case the other SSG doesn't invoke the
|
|
// callback and complete this SSG.
|
|
addTimeout();
|
|
|
|
return transactionReadyCallback;
|
|
}
|
|
|
|
private class ISurfaceSyncGroupImpl extends ISurfaceSyncGroup.Stub {
|
|
@Override
|
|
public boolean onAddedToSyncGroup(IBinder parentSyncGroupToken,
|
|
boolean parentSyncGroupMerge) {
|
|
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
|
|
Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_VIEW, mTrackName,
|
|
"onAddedToSyncGroup token=" + parentSyncGroupToken.hashCode(), hashCode());
|
|
}
|
|
boolean didAdd = addSyncToWm(parentSyncGroupToken, parentSyncGroupMerge, null);
|
|
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
|
|
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode());
|
|
}
|
|
return didAdd;
|
|
}
|
|
|
|
@Override
|
|
public boolean addToSync(ISurfaceSyncGroup surfaceSyncGroup, boolean parentSyncGroupMerge) {
|
|
return SurfaceSyncGroup.this.add(surfaceSyncGroup, parentSyncGroupMerge,
|
|
null /* runnable */);
|
|
}
|
|
|
|
SurfaceSyncGroup getSurfaceSyncGroup() {
|
|
return SurfaceSyncGroup.this;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
public void toggleTimeout(boolean enable) {
|
|
synchronized (mLock) {
|
|
mTimeoutDisabled = !enable;
|
|
if (mTimeoutAdded && !enable) {
|
|
mHandler.removeCallbacksAndMessages(this);
|
|
mTimeoutAdded = false;
|
|
} else if (!mTimeoutAdded && enable) {
|
|
addTimeout();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void addTimeout() {
|
|
Looper looper = null;
|
|
synchronized (sHandlerThreadLock) {
|
|
if (sHandlerThread == null) {
|
|
sHandlerThread = new HandlerThread("SurfaceSyncGroupTimer");
|
|
sHandlerThread.start();
|
|
}
|
|
|
|
looper = sHandlerThread.getLooper();
|
|
}
|
|
|
|
synchronized (mLock) {
|
|
if (mTimeoutAdded || mTimeoutDisabled || looper == null) {
|
|
// We only need one timeout for the entire SurfaceSyncGroup since we just want to
|
|
// ensure it doesn't stay stuck forever.
|
|
return;
|
|
}
|
|
|
|
if (mHandler == null) {
|
|
mHandler = new Handler(looper);
|
|
}
|
|
|
|
mTimeoutAdded = true;
|
|
}
|
|
|
|
Runnable runnable = () -> {
|
|
Log.e(TAG, "Failed to receive transaction ready in " + TRANSACTION_READY_TIMEOUT
|
|
+ "ms. Marking SurfaceSyncGroup(" + mName + ") as ready");
|
|
// Clear out any pending syncs in case the other syncs can't complete or timeout due to
|
|
// a crash.
|
|
synchronized (mLock) {
|
|
mPendingSyncs.clear();
|
|
}
|
|
markSyncReady();
|
|
};
|
|
mHandler.postDelayed(runnable, this, TRANSACTION_READY_TIMEOUT);
|
|
}
|
|
|
|
/**
|
|
* A frame callback that is used to synchronize SurfaceViews. The owner of the SurfaceView must
|
|
* implement onFrameStarted when trying to sync the SurfaceView. This is to ensure the sync
|
|
* knows when the frame is ready to add to the sync.
|
|
*
|
|
* @hide
|
|
*/
|
|
public interface SurfaceViewFrameCallback {
|
|
/**
|
|
* Called when the SurfaceView is going to render a frame
|
|
*/
|
|
void onFrameStarted();
|
|
}
|
|
}
|