summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Chavi Weingarten <chaviw@google.com> 2022-10-28 19:54:51 +0000
committer Chavi Weingarten <chaviw@google.com> 2023-01-06 17:16:51 +0000
commitb8afa7f7b99936e4313608965765dfaf6863cce2 (patch)
tree20c2448ab89a3631be6fc5fa8a2f62c94c433a73
parent3ad4f200c29703cf36ebc88f2f9c6f738cf7ee41 (diff)
Make SurfaceSyncGroup AIDL interface and add cross process syncs
When two processes are involved in a sync, we don't want them to exchange Transaction info because it may contain SurfaceControls that the two transactions shouldn't exchange. Instead, have system server take care of the cross process syncs and ensure SurfaceSyncGroup sends the data to system server instead of to the two apps. This is done by the following flow: 1. P1 creates a SyncGroup 2. P1 adds something local to the SyncGroup. No WM API is called. 3. P1 adds something in P2 to SyncGroup 3a. P1 creates a SyncGroup in WM with P1's SyncGroup as token 3b. P1 notifies P2 that it should be added to the SyncGroup with a specified token 3c. P2 invokes WM call and adds itself to the same SyncGroup in WM When P1 and P2 groups are done, they will invoke the transaction callback to WM, who will merge into a single transaction 4. P1 marks SyncGroup as ready, which calls into WM to mark the associated SyncGroup as ready. When all callbacks are complete, it will apply the final transaction Test: SurfaceSyncGroupContinuousTest Bug: 237804605 Change-Id: I0f52f2e2d1a95e0ecc2b95b6dddd2ebb5b3edb54
-rw-r--r--core/java/android/view/ISurfaceControlViewHost.aidl14
-rw-r--r--core/java/android/view/IWindowManager.aidl16
-rw-r--r--core/java/android/view/SurfaceControlViewHost.java18
-rw-r--r--core/java/android/view/SurfaceView.java5
-rw-r--r--core/java/android/view/ViewRootImpl.java71
-rw-r--r--core/java/android/window/AddToSurfaceSyncGroupResult.aidl37
-rw-r--r--core/java/android/window/ISurfaceSyncGroup.aidl55
-rw-r--r--core/java/android/window/ISurfaceSyncGroupCompletedListener.aidl33
-rw-r--r--core/java/android/window/ITransactionReadyCallback.aidl37
-rw-r--r--core/java/android/window/SurfaceSyncGroup.java505
-rw-r--r--core/java/android/window/SurfaceSyncGroup.md65
-rw-r--r--services/core/java/com/android/server/wm/SurfaceSyncGroupController.java101
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java18
-rw-r--r--services/tests/wmtests/Android.bp19
-rw-r--r--services/tests/wmtests/AndroidManifest.xml3
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupContinuousTest.java14
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java35
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/scvh/EmbeddedSCVHService.java127
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/scvh/IAttachEmbeddedWindow.aidl27
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/scvh/IAttachEmbeddedWindowCallback.aidl23
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/scvh/SyncValidatorSCVHTestCase.java234
-rw-r--r--tests/SurfaceControlViewHostTest/Android.bp5
-rw-r--r--tests/SurfaceControlViewHostTest/AndroidManifest.xml10
-rw-r--r--tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/EmbeddedWindowService.java109
-rw-r--r--tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/IAttachEmbeddedWindow.aidl26
-rw-r--r--tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/IAttachEmbeddedWindowCallback.aidl23
-rw-r--r--tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostSyncTest.java215
27 files changed, 1626 insertions, 219 deletions
diff --git a/core/java/android/view/ISurfaceControlViewHost.aidl b/core/java/android/view/ISurfaceControlViewHost.aidl
index bf72a307220d..15008ae18618 100644
--- a/core/java/android/view/ISurfaceControlViewHost.aidl
+++ b/core/java/android/view/ISurfaceControlViewHost.aidl
@@ -19,13 +19,19 @@ package android.view;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.view.InsetsState;
+import android.window.ISurfaceSyncGroup;
/**
* API from content embedder back to embedded content in SurfaceControlViewHost
* {@hide}
*/
-oneway interface ISurfaceControlViewHost {
- void onConfigurationChanged(in Configuration newConfig);
- void onDispatchDetachedFromWindow();
- void onInsetsChanged(in InsetsState state, in Rect insetFrame);
+interface ISurfaceControlViewHost {
+ /**
+ * TODO (b/263273252): Investigate the need for these to be blocking calls or add additional
+ * APIs that are blocking
+ */
+ oneway void onConfigurationChanged(in Configuration newConfig);
+ oneway void onDispatchDetachedFromWindow();
+ oneway void onInsetsChanged(in InsetsState state, in Rect insetFrame);
+ ISurfaceSyncGroup getSurfaceSyncGroup();
}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 0aba80db5378..e2882256a0a7 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -65,6 +65,8 @@ import android.view.WindowManager;
import android.view.SurfaceControl;
import android.view.displayhash.DisplayHash;
import android.view.displayhash.VerifiedDisplayHash;
+import android.window.AddToSurfaceSyncGroupResult;
+import android.window.ISurfaceSyncGroupCompletedListener;
import android.window.ITaskFpsCallback;
import android.window.ScreenCapture;
@@ -985,4 +987,18 @@ interface IWindowManager
* @return {@code true} if the key will be handled globally.
*/
boolean isGlobalKey(int keyCode);
+
+ /**
+ * Create or add to a SurfaceSyncGroup in WindowManager. WindowManager maintains some
+ * SurfaceSyncGroups to ensure multiple processes can sync with each other without sharing
+ * SurfaceControls
+ */
+ boolean addToSurfaceSyncGroup(in IBinder syncGroupToken, boolean parentSyncGroupMerge,
+ in @nullable ISurfaceSyncGroupCompletedListener completedListener,
+ out AddToSurfaceSyncGroupResult addToSurfaceSyncGroupResult);
+
+ /**
+ * Mark a SurfaceSyncGroup stored in WindowManager as ready.
+ */
+ oneway void markSurfaceSyncGroupReady(in IBinder syncGroupToken);
}
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index 50ce7b634e99..1e7e17fa54f6 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -29,9 +29,14 @@ import android.os.Parcelable;
import android.os.RemoteException;
import android.util.Log;
import android.view.accessibility.IAccessibilityEmbeddedConnection;
+import android.window.ISurfaceSyncGroup;
import android.window.WindowTokenClient;
import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
/**
* Utility class for adding a View hierarchy to a {@link SurfaceControl}. The View hierarchy
@@ -87,6 +92,19 @@ public class SurfaceControlViewHost {
}
mWm.setInsetsState(state);
}
+
+ @Override
+ public ISurfaceSyncGroup getSurfaceSyncGroup() {
+ CompletableFuture<ISurfaceSyncGroup> surfaceSyncGroup = new CompletableFuture<>();
+ mViewRoot.mHandler.post(
+ () -> surfaceSyncGroup.complete(mViewRoot.getOrCreateSurfaceSyncGroup()));
+ try {
+ return surfaceSyncGroup.get(1, TimeUnit.SECONDS);
+ } catch (InterruptedException | ExecutionException | TimeoutException e) {
+ Log.e(TAG, "Failed to get SurfaceSyncGroup for SCVH", e);
+ }
+ return null;
+ }
}
private ISurfaceControlViewHost mRemoteInterface = new ISurfaceControlViewHostImpl();
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index e38376d7d4a1..37e7b2e41fa7 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -1091,7 +1091,8 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
t = syncBufferTransactionCallback.waitForTransaction();
}
- surfaceSyncGroup.onTransactionReady(t);
+ surfaceSyncGroup.addTransactionToSync(t);
+ surfaceSyncGroup.markSyncReady();
onDrawFinished();
});
}
@@ -1106,7 +1107,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
synchronized (mSyncGroups) {
mSyncGroups.remove(surfaceSyncGroup);
}
- surfaceSyncGroup.onTransactionReady(null);
+ surfaceSyncGroup.markSyncReady();
onDrawFinished();
});
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index c0f4731aeaf4..e091b32237ba 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -231,6 +231,7 @@ import java.util.Objects;
import java.util.Queue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
* The top of a view hierarchy, implementing the needed protocol between View
@@ -863,6 +864,8 @@ public final class ViewRootImpl implements ViewParent,
// animations until all are done.
private static int sNumSyncsInProgress = 0;
+ private int mNumPausedForSync = 0;
+
private HashSet<ScrollCaptureCallback> mRootScrollCaptureCallbacks;
private long mScrollCaptureRequestTimeout = SCROLL_CAPTURE_REQUEST_TIMEOUT_MILLIS;
@@ -2811,6 +2814,19 @@ public final class ViewRootImpl implements ViewParent,
return;
}
+ if (mNumPausedForSync > 0) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.instant(Trace.TRACE_TAG_VIEW,
+ TextUtils.formatSimple("performTraversals#mNumPausedForSync=%d",
+ mNumPausedForSync));
+ }
+ if (DEBUG_BLAST) {
+ Log.d(mTag, "Skipping traversal due to sync " + mNumPausedForSync);
+ }
+ mLastPerformTraversalsSkipDrawReason = "paused_for_sync";
+ return;
+ }
+
mIsInTraversal = true;
mWillDrawSoon = true;
boolean cancelDraw = false;
@@ -3677,7 +3693,7 @@ public final class ViewRootImpl implements ViewParent,
}
if (mActiveSurfaceSyncGroup != null) {
- mActiveSurfaceSyncGroup.onTransactionReady(null);
+ mActiveSurfaceSyncGroup.markSyncReady();
}
} else if (cancelAndRedraw) {
mLastPerformTraversalsSkipDrawReason = cancelDueToPreDrawListener
@@ -3693,7 +3709,7 @@ public final class ViewRootImpl implements ViewParent,
mPendingTransitions.clear();
}
if (!performDraw() && mActiveSurfaceSyncGroup != null) {
- mActiveSurfaceSyncGroup.onTransactionReady(null);
+ mActiveSurfaceSyncGroup.markSyncReady();
}
}
@@ -3710,7 +3726,7 @@ public final class ViewRootImpl implements ViewParent,
mActiveSurfaceSyncGroup = null;
mSyncBuffer = false;
if (isInWMSRequestedSync()) {
- mWmsRequestSyncGroup.onTransactionReady(null);
+ mWmsRequestSyncGroup.markSyncReady();
mWmsRequestSyncGroup = null;
mWmsRequestSyncGroupState = WMS_SYNC_NONE;
}
@@ -4553,7 +4569,7 @@ public final class ViewRootImpl implements ViewParent,
if (mSurfaceHolder != null && mSurface.isValid()) {
final SurfaceSyncGroup surfaceSyncGroup = mActiveSurfaceSyncGroup;
SurfaceCallbackHelper sch = new SurfaceCallbackHelper(() ->
- mHandler.post(() -> surfaceSyncGroup.onTransactionReady(null)));
+ mHandler.post(() -> surfaceSyncGroup.markSyncReady()));
mActiveSurfaceSyncGroup = null;
SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
@@ -4566,7 +4582,7 @@ public final class ViewRootImpl implements ViewParent,
}
}
if (mActiveSurfaceSyncGroup != null && !usingAsyncReport) {
- mActiveSurfaceSyncGroup.onTransactionReady(null);
+ mActiveSurfaceSyncGroup.markSyncReady();
}
if (mPerformContentCapture) {
performContentCaptureInitialReport();
@@ -11281,8 +11297,9 @@ public final class ViewRootImpl implements ViewParent,
// pendingDrawFinished.
if ((syncResult
& (SYNC_LOST_SURFACE_REWARD_IF_FOUND | SYNC_CONTEXT_IS_STOPPED)) != 0) {
- surfaceSyncGroup.onTransactionReady(
+ surfaceSyncGroup.addTransactionToSync(
mBlastBufferQueue.gatherPendingTransactions(frame));
+ surfaceSyncGroup.markSyncReady();
return null;
}
@@ -11291,8 +11308,13 @@ public final class ViewRootImpl implements ViewParent,
}
if (syncBuffer) {
- mBlastBufferQueue.syncNextTransaction(
- surfaceSyncGroup::onTransactionReady);
+ mBlastBufferQueue.syncNextTransaction(new Consumer<Transaction>() {
+ @Override
+ public void accept(Transaction transaction) {
+ surfaceSyncGroup.addTransactionToSync(transaction);
+ surfaceSyncGroup.markSyncReady();
+ }
+ });
}
return didProduceBuffer -> {
@@ -11312,8 +11334,9 @@ public final class ViewRootImpl implements ViewParent,
// since the frame didn't draw on this vsync. It's possible the frame will
// draw later, but it's better to not be sync than to block on a frame that
// may never come.
- surfaceSyncGroup.onTransactionReady(
+ surfaceSyncGroup.addTransactionToSync(
mBlastBufferQueue.gatherPendingTransactions(frame));
+ surfaceSyncGroup.markSyncReady();
return;
}
@@ -11321,22 +11344,41 @@ public final class ViewRootImpl implements ViewParent,
// syncNextTransaction callback. Instead, just report back to the Syncer so it
// knows that this sync request is complete.
if (!syncBuffer) {
- surfaceSyncGroup.onTransactionReady(null);
+ surfaceSyncGroup.markSyncReady();
}
};
}
});
}
+ private class VRISurfaceSyncGroup extends SurfaceSyncGroup {
+ VRISurfaceSyncGroup(String name) {
+ super(name);
+ }
+
+ @Override
+ public void onSyncReady() {
+ Runnable runnable = () -> {
+ mNumPausedForSync--;
+ if (!mIsInTraversal && mNumPausedForSync == 0) {
+ scheduleTraversals();
+ }
+ };
+
+ if (Thread.currentThread() == mThread) {
+ runnable.run();
+ } else {
+ mHandler.post(runnable);
+ }
+ }
+ }
+
@Override
public SurfaceSyncGroup getOrCreateSurfaceSyncGroup() {
boolean newSyncGroup = false;
if (mActiveSurfaceSyncGroup == null) {
- mActiveSurfaceSyncGroup = new SurfaceSyncGroup(mTag);
+ mActiveSurfaceSyncGroup = new VRISurfaceSyncGroup(mTag);
updateSyncInProgressCount(mActiveSurfaceSyncGroup);
- if (!mIsInTraversal && !mTraversalScheduled) {
- scheduleTraversals();
- }
newSyncGroup = true;
}
@@ -11352,6 +11394,7 @@ public final class ViewRootImpl implements ViewParent,
}
}
+ mNumPausedForSync++;
return mActiveSurfaceSyncGroup;
};
diff --git a/core/java/android/window/AddToSurfaceSyncGroupResult.aidl b/core/java/android/window/AddToSurfaceSyncGroupResult.aidl
new file mode 100644
index 000000000000..ea38e656c16f
--- /dev/null
+++ b/core/java/android/window/AddToSurfaceSyncGroupResult.aidl
@@ -0,0 +1,37 @@
+/*
+ * 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.window.ISurfaceSyncGroup;
+import android.window.ITransactionReadyCallback;
+
+/**
+ * Data class that contains the results when adding a SufarceSyncGroup to WindowManagerService
+ * @hide
+ */
+parcelable AddToSurfaceSyncGroupResult {
+ /**
+ * The ISurfaceSyncGroup that the child was added to.
+ */
+ ISurfaceSyncGroup mParentSyncGroup;
+
+ /**
+ * The ITransactionReadyCallback that should be called when the child SurfaceSyncGroup
+ * completes.
+ */
+ ITransactionReadyCallback mTransactionReadyCallback;
+} \ No newline at end of file
diff --git a/core/java/android/window/ISurfaceSyncGroup.aidl b/core/java/android/window/ISurfaceSyncGroup.aidl
new file mode 100644
index 000000000000..c60133b8cb79
--- /dev/null
+++ b/core/java/android/window/ISurfaceSyncGroup.aidl
@@ -0,0 +1,55 @@
+/*
+ * 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.os.IBinder;
+
+/**
+ * An ISurfaceSyncGroup that can be added to another ISurfaceSyncGroup or is the root
+ * ISurfaceSyncGroup.
+ *
+ * See SurfaceSyncGroup.md
+ *
+ * {@hide}
+ */
+interface ISurfaceSyncGroup {
+ /**
+ * Called when the ISurfaceSyncGroup is ready to begin handling a sync request. When invoked,
+ * the implementor should set up the {@link android.window.ITransactionReadyCallback}, either
+ * via system server or in the local process.
+ *
+ * @param parentSyncGroup The parent that added this ISurfaceSyncGroup
+ * @param parentSyncGroupMerge true if the ISurfaceSyncGroup is added because its child was
+ * added to a new SurfaceSyncGroup.
+ * @return true if it was successfully added to the sync, false otherwise.
+ */
+ boolean onAddedToSyncGroup(in IBinder parentSyncGroupToken, boolean parentSyncGroupMerge);
+
+ /**
+ * Call to add a ISurfaceSyncGroup to this ISurfaceSyncGroup. This is adding a child
+ * ISurfaceSyncGroup so this group can't complete until the child does.
+ *
+ * @param The child ISurfaceSyncGroup to add to this ISurfaceSyncGroup.
+ * @param parentSyncGroupMerge true if the current 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.
+ * @return true if it was successfully added to the sync, false otherwise.
+ */
+ boolean addToSync(in ISurfaceSyncGroup surfaceSyncGroup, boolean parentSyncGroupMerge);
+} \ No newline at end of file
diff --git a/core/java/android/window/ISurfaceSyncGroupCompletedListener.aidl b/core/java/android/window/ISurfaceSyncGroupCompletedListener.aidl
new file mode 100644
index 000000000000..cac1079c8ddd
--- /dev/null
+++ b/core/java/android/window/ISurfaceSyncGroupCompletedListener.aidl
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+/**
+ * A listener used to notify when a SurfaceSyncGroup has completed. This doesn't indicate anything
+ * about the state of what's on screen, but means everything in the SurfaceSyncGroup has
+ * completed, including all children. This is similar to
+ * {@link SurfaceSyncGroup#addSyncCompleteCallback}, except allows the callback to be invoked to
+ * another process.
+ *
+ * @hide
+ */
+interface ISurfaceSyncGroupCompletedListener {
+ /**
+ * Invoked when the SurfaceSyncGroup has completed.
+ */
+ oneway void onSurfaceSyncGroupComplete();
+} \ No newline at end of file
diff --git a/core/java/android/window/ITransactionReadyCallback.aidl b/core/java/android/window/ITransactionReadyCallback.aidl
new file mode 100644
index 000000000000..36b1579e6acf
--- /dev/null
+++ b/core/java/android/window/ITransactionReadyCallback.aidl
@@ -0,0 +1,37 @@
+/*
+ * 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.view.SurfaceControl.Transaction;
+
+/**
+ * Interface that is invoked when the ISurfaceSyncGroup has completed. The parent ISurfaceSyncGroup
+ * creates an ITransactionReadyCallback and sends it to the children who will invoke the
+ * {@link onTransactionReady} when they have completed, including waiting on their children.
+ *
+ * @hide
+ */
+interface ITransactionReadyCallback {
+ /**
+ * Invoked when ISurfaceSyncGroup has completed. This means the ISurfaceSyncGroup has been
+ * marked as ready and all children it was waiting on have been completed.
+ *
+ * @param t The transaction that contains everything to be included in the sync. This can be
+ null if there's nothing to sync
+ */
+ void onTransactionReady(in @nullable Transaction t);
+} \ No newline at end of file
diff --git a/core/java/android/window/SurfaceSyncGroup.java b/core/java/android/window/SurfaceSyncGroup.java
index e1a9a48c76b7..12cd3408bf87 100644
--- a/core/java/android/window/SurfaceSyncGroup.java
+++ b/core/java/android/window/SurfaceSyncGroup.java
@@ -16,20 +16,26 @@
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.Debug;
+import android.os.IBinder;
+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 java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
@@ -37,43 +43,13 @@ import java.util.function.Supplier;
/**
* Used to organize syncs for surfaces.
- *
- * The SurfaceSyncGroup allows callers to add desired syncs into a set and wait for them to all
- * complete before getting a callback. The purpose of the SurfaceSyncGroup is to be an accounting
- * mechanism so each sync implementation doesn't need to handle it themselves. The SurfaceSyncGroup
- * class is used the following way.
- *
- * 1. {@link #addToSync(SurfaceSyncGroup, boolean)} is called for every SurfaceSyncGroup object that
- * wants to be included in the sync. If the addSync is called for an {@link AttachedSurfaceControl}
- * or {@link SurfaceView} it needs to be called on the UI thread. When addToSync is called, it's
- * guaranteed that any UI updates that were requested before addToSync but after the last frame
- * drew, will be included in the sync.
- * 2. {@link #markSyncReady()} should be called when all the {@link SurfaceSyncGroup}s have been
- * added to the SurfaceSyncGroup. At this point, the SurfaceSyncGroup is closed and no more
- * SurfaceSyncGroups can be added to it.
- * 3. The SurfaceSyncGroup will gather the data for each SurfaceSyncGroup using the steps described
- * below. When all the SurfaceSyncGroups have finished, the syncRequestComplete will be invoked and
- * the transaction will either be applied or sent to the caller. In most cases, only the
- * SurfaceSyncGroup should be handling the Transaction object directly. However, there are some
- * cases where the framework needs to send the Transaction elsewhere, like in ViewRootImpl, so that
- * option is provided.
- *
- * The following is what happens within the {@link android.window.SurfaceSyncGroup}
- * 1. Each SurfaceSyncGroup will get a
- * {@link SurfaceSyncGroup#onAddedToSyncGroup(SurfaceSyncGroup, TransactionReadyCallback)} callback
- * that contains a {@link TransactionReadyCallback}.
- * 2. Each {@link SurfaceSyncGroup} needs to invoke
- * {@link SurfaceSyncGroup#onTransactionReady(Transaction)}.
- * This makes sure the parent SurfaceSyncGroup knows when the SurfaceSyncGroup is complete, allowing
- * the parent SurfaceSyncGroup to get the Transaction that contains the changes for the child
- * SurfaceSyncGroup
- * 3. When the final TransactionReadyCallback finishes for the child SurfaceSyncGroups, the
- * transaction is either applied if it's the top most parent or the final merged transaction is sent
- * up to its parent SurfaceSyncGroup.
+ * </p>
+ * See SurfaceSyncGroup.md
+ * </p>
*
* @hide
*/
-public class SurfaceSyncGroup {
+public class SurfaceSyncGroup extends ISurfaceSyncGroup.Stub {
private static final String TAG = "SurfaceSyncGroup";
private static final boolean DEBUG = false;
@@ -92,7 +68,7 @@ public class SurfaceSyncGroup {
private final String mName;
@GuardedBy("mLock")
- private final Set<TransactionReadyCallback> mPendingSyncs = new ArraySet<>();
+ private final ArraySet<ITransactionReadyCallback> mPendingSyncs = new ArraySet<>();
@GuardedBy("mLock")
private final Transaction mTransaction = sTransactionFactory.get();
@GuardedBy("mLock")
@@ -102,14 +78,38 @@ public class SurfaceSyncGroup {
private boolean mFinished;
@GuardedBy("mLock")
- private TransactionReadyCallback mTransactionReadyCallback;
+ private Consumer<Transaction> mTransactionReadyConsumer;
@GuardedBy("mLock")
- private SurfaceSyncGroup mParentSyncGroup;
+ private ISurfaceSyncGroup mParentSyncGroup;
@GuardedBy("mLock")
private final ArraySet<Pair<Executor, Runnable>> mSyncCompleteCallbacks = new ArraySet<>();
+ @GuardedBy("mLock")
+ private boolean mHasWMSync;
+
+ @GuardedBy("mLock")
+ private ISurfaceSyncGroupCompletedListener mSurfaceSyncGroupCompletedListener;
+
+ /**
+ * 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 boolean isLocalBinder(IBinder binder) {
+ return !(binder instanceof BinderProxy);
+ }
+
+ private static SurfaceSyncGroup getSurfaceSyncGroup(ISurfaceSyncGroup iSurfaceSyncGroup) {
+ if (iSurfaceSyncGroup instanceof SurfaceSyncGroup) {
+ return (SurfaceSyncGroup) iSurfaceSyncGroup;
+ }
+ return null;
+ }
+
/**
* @hide
*/
@@ -129,20 +129,19 @@ public class SurfaceSyncGroup {
transaction.apply();
}
});
-
}
/**
* Creates a sync.
*
- * @param transactionReadyCallback The complete callback that contains the syncId and
+ * @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> transactionReadyCallback) {
+ 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
@@ -153,17 +152,19 @@ public class SurfaceSyncGroup {
mName = name + "#" + sCounter.getAndIncrement();
- mTransactionReadyCallback = transaction -> {
+ mTransactionReadyConsumer = (transaction) -> {
if (DEBUG && transaction != null) {
Log.d(TAG, "Sending non null transaction " + transaction + " to callback for "
+ mName);
}
Trace.instant(Trace.TRACE_TAG_VIEW,
"Final TransactionCallback with " + transaction + " for " + mName);
- transactionReadyCallback.accept(transaction);
+ transactionReadyConsumer.accept(transaction);
synchronized (mLock) {
- for (Pair<Executor, Runnable> callback : mSyncCompleteCallbacks) {
- callback.first.execute(callback.second);
+ // 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) {
+ invokeSyncCompleteListeners();
}
}
};
@@ -175,6 +176,13 @@ public class SurfaceSyncGroup {
}
}
+ @GuardedBy("mLock")
+ private void invokeSyncCompleteListeners() {
+ mSyncCompleteCallbacks.forEach(
+ executorRunnablePair -> executorRunnablePair.first.execute(
+ executorRunnablePair.second));
+ }
+
/**
* Add a {@link Runnable} to be executed when the sync completes.
*
@@ -194,22 +202,15 @@ public class SurfaceSyncGroup {
* set have completed their sync
*/
public void markSyncReady() {
- onTransactionReady(null);
- }
-
- /**
- * Similar to {@link #markSyncReady()}, but a transaction is passed in to merge with the
- * SurfaceSyncGroup.
- *
- * @param t The transaction that merges into the main Transaction for the SurfaceSyncGroup.
- */
- public void onTransactionReady(@Nullable Transaction t) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "markSyncReady " + mName);
synchronized (mLock) {
- mSyncReady = true;
- if (t != null) {
- mTransaction.merge(t);
+ if (mHasWMSync) {
+ try {
+ WindowManagerGlobal.getWindowManagerService().markSurfaceSyncGroupReady(mToken);
+ } catch (RemoteException e) {
+ }
}
+ mSyncReady = true;
checkIfSyncIsComplete();
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
@@ -232,21 +233,40 @@ public class SurfaceSyncGroup {
Consumer<SurfaceViewFrameCallback> frameCallbackConsumer) {
SurfaceSyncGroup surfaceSyncGroup = new SurfaceSyncGroup(surfaceView.getName());
if (addToSync(surfaceSyncGroup, false /* parentSyncGroupMerge */)) {
- frameCallbackConsumer.accept(
- () -> surfaceView.syncNextFrame(surfaceSyncGroup::onTransactionReady));
+ frameCallbackConsumer.accept(() -> surfaceView.syncNextFrame(transaction -> {
+ surfaceSyncGroup.addTransactionToSync(transaction);
+ surfaceSyncGroup.markSyncReady();
+ }));
return true;
}
return false;
}
/**
- * Add a View's rootView to a sync set.
+ * Add an AttachedSurfaceControl to a sync set.
*
- * @param viewRoot The viewRoot that will be add to the sync set
+ * @param viewRoot The viewRoot that will be add to the sync set.
* @return true if the View was successfully added to the SyncGroup, false otherwise.
+ * @see #addToSync(AttachedSurfaceControl, Runnable)
*/
@UiThread
public boolean addToSync(@Nullable AttachedSurfaceControl viewRoot) {
+ return addToSync(viewRoot, null /* runnable */);
+ }
+
+ /**
+ * Add an AttachedSurfaceControl to a sync set. The AttachedSurfaceControl will pause rendering
+ * to ensure the runnable can be invoked and the sync picks up the frame that contains the
+ * changes.
+ *
+ * @param viewRoot The viewRoot that will be add to the sync set.
+ * @param runnable The runnable to be invoked before adding to the sync group.
+ * @return true if the View was successfully added to the SyncGroup, false otherwise.
+ * @see #addToSync(AttachedSurfaceControl)
+ */
+ @UiThread
+ public boolean addToSync(@Nullable AttachedSurfaceControl viewRoot,
+ @Nullable Runnable runnable) {
if (viewRoot == null) {
return false;
}
@@ -254,67 +274,133 @@ public class SurfaceSyncGroup {
if (surfaceSyncGroup == null) {
return false;
}
- return addToSync(surfaceSyncGroup, false /* parentSyncGroupMerge */);
+
+ return addToSync(surfaceSyncGroup, false /* parentSyncGroupMerge */, runnable);
+ }
+
+ /**
+ * Helper method to add a SurfaceControlViewHost.SurfacePackage to the sync group. 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 should be synced
+ * @param runnable The Runnable that's invoked before getting the frame to sync.
+ * @return true if the SCVH was successfully added to the current SyncGroup, false
+ * otherwise.
+ */
+ public boolean addToSync(@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 addToSync(surfaceSyncGroup, false /* parentSyncGroupMerge */, runnable);
+ }
+
+ @Override
+ public boolean addToSync(ISurfaceSyncGroup surfaceSyncGroup, boolean parentSyncGroupMerge) {
+ return addToSync(surfaceSyncGroup, parentSyncGroupMerge, null);
}
/**
* Add a {@link SurfaceSyncGroup} to a sync set. The sync set will wait for all
* SyncableSurfaces to complete before notifying.
*
- * @param surfaceSyncGroup A SyncableSurface that implements how to handle syncing
- * buffers.
+ * @param surfaceSyncGroup A SyncableSurface that implements how to handle syncing
+ * buffers.
+ * @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.
*/
- public boolean addToSync(SurfaceSyncGroup surfaceSyncGroup, boolean parentSyncGroupMerge) {
+ public boolean addToSync(ISurfaceSyncGroup surfaceSyncGroup, boolean parentSyncGroupMerge,
+ @Nullable Runnable runnable) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW,
- "addToSync child=" + surfaceSyncGroup.mName + " parent=" + mName);
- TransactionReadyCallback transactionReadyCallback = new TransactionReadyCallback() {
- @Override
- public void onTransactionReady(Transaction t) {
- if (DEBUG) {
- Log.d(TAG, "onTransactionReady called for" + surfaceSyncGroup.mName
- + " and sent to " + mName);
- }
- synchronized (mLock) {
- if (t != null) {
- // 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);
- Trace.instant(Trace.TRACE_TAG_VIEW,
- "onTransactionReady child=" + surfaceSyncGroup.mName + " parent="
- + mName);
- checkIfSyncIsComplete();
- }
- }
- };
-
+ "addToSync token=" + mToken.hashCode() + " parent=" + mName);
synchronized (mLock) {
if (mSyncReady) {
- Log.e(TAG, "Sync " + mName + " was already marked as ready. No more "
- + "SurfaceSyncGroups can be added.");
+ Log.w(TAG, "Trying to add to sync when already marked as ready " + mName);
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
return false;
}
- mPendingSyncs.add(transactionReadyCallback);
- if (DEBUG) {
- Log.d(TAG, "addToSync " + surfaceSyncGroup.mName + " to " + mName + " mSyncReady="
- + mSyncReady + " mPendingSyncs=" + mPendingSyncs.size());
+ }
+
+ if (runnable != null) {
+ runnable.run();
+ }
+
+ if (isLocalBinder(surfaceSyncGroup.asBinder())) {
+ boolean didAddLocalSync = addLocalSync(surfaceSyncGroup, parentSyncGroupMerge);
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ 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) {
+ invokeSyncCompleteListeners();
+ }
+ }
+ };
+ if (!addSyncToWm(mToken, false /* parentSyncGroupMerge */,
+ mSurfaceSyncGroupCompletedListener)) {
+ mSurfaceSyncGroupCompletedListener = null;
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ return false;
+ }
+ mHasWMSync = true;
}
}
- surfaceSyncGroup.onAddedToSyncGroup(this, transactionReadyCallback);
+ try {
+ surfaceSyncGroup.onAddedToSyncGroup(mToken, parentSyncGroupMerge);
+ } catch (RemoteException e) {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ return false;
+ }
+
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
return true;
}
+ @Override
+ public final boolean onAddedToSyncGroup(IBinder parentSyncGroupToken,
+ boolean parentSyncGroupMerge) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW,
+ "onAddedToSyncGroup token=" + parentSyncGroupToken.hashCode() + " child=" + mName);
+ boolean didAdd = addSyncToWm(parentSyncGroupToken, parentSyncGroupMerge, null);
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ return didAdd;
+ }
+
+
/**
* Add a Transaction to this sync set. This allows the caller to provide other info that
* should be synced with the transactions.
@@ -325,39 +411,75 @@ public class SurfaceSyncGroup {
}
}
- @GuardedBy("mLock")
- private void checkIfSyncIsComplete() {
- if (mFinished) {
- if (DEBUG) {
- Log.d(TAG, "SurfaceSyncGroup=" + mName + " is already complete");
- }
- return;
- }
+ /**
+ * Invoked when the SurfaceSyncGroup has been added to another SurfaceSyncGroup and is ready
+ * to proceed.
+ */
+ public void onSyncReady() {
+ }
- Trace.instant(Trace.TRACE_TAG_VIEW,
- "checkIfSyncIsComplete " + mName + " mSyncReady=" + mSyncReady + " mPendingSyncs="
- + mPendingSyncs.size());
- if (!mSyncReady || !mPendingSyncs.isEmpty()) {
+ private boolean addSyncToWm(IBinder token, boolean parentSyncGroupMerge,
+ @Nullable ISurfaceSyncGroupCompletedListener surfaceSyncGroupCompletedListener) {
+ try {
if (DEBUG) {
- Log.d(TAG,
- "SurfaceSyncGroup=" + mName + " is not complete. mSyncReady=" + mSyncReady
- + " mPendingSyncs=" + mPendingSyncs.size());
+ Log.d(TAG, "Attempting to add remote sync to " + mName
+ + ". Setting up Sync in WindowManager.");
}
- return;
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW,
+ "addSyncToWm=" + token.hashCode() + " group=" + mName);
+ AddToSurfaceSyncGroupResult addToSyncGroupResult = new AddToSurfaceSyncGroupResult();
+ if (!WindowManagerGlobal.getWindowManagerService().addToSurfaceSyncGroup(token,
+ parentSyncGroupMerge, surfaceSyncGroupCompletedListener,
+ addToSyncGroupResult)) {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ return false;
+ }
+
+ setTransactionCallbackFromParent(addToSyncGroupResult.mParentSyncGroup,
+ addToSyncGroupResult.mTransactionReadyCallback);
+ } catch (RemoteException e) {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ return false;
}
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ return true;
+ }
+ private boolean addLocalSync(ISurfaceSyncGroup childSyncToken, boolean parentSyncGroupMerge) {
if (DEBUG) {
- Log.d(TAG, "Successfully finished sync id=" + mName);
+ Log.d(TAG, "Adding local sync " + 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;
}
- mTransactionReadyCallback.onTransactionReady(mTransaction);
- mFinished = true;
- }
- private void onAddedToSyncGroup(SurfaceSyncGroup parentSyncGroup,
- TransactionReadyCallback transactionReadyCallback) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW,
- "onAddedToSyncGroup child=" + mName + " parent=" + parentSyncGroup.mName);
+ "addLocalSync=" + childSurfaceSyncGroup.mName + " parent=" + mName);
+ ITransactionReadyCallback callback =
+ createTransactionReadyCallback(parentSyncGroupMerge);
+
+ if (callback == null) {
+ return false;
+ }
+
+ childSurfaceSyncGroup.setTransactionCallbackFromParent(this, callback);
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ return true;
+ }
+
+ private void setTransactionCallbackFromParent(ISurfaceSyncGroup parentSyncGroup,
+ ITransactionReadyCallback transactionReadyCallback) {
+ if (DEBUG) {
+ Log.d(TAG, "setTransactionCallbackFromParent " + mName);
+ }
boolean finished = false;
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW,
+ "setTransactionCallbackFromParent " + mName + " callback="
+ + transactionReadyCallback.hashCode());
synchronized (mLock) {
if (mFinished) {
finished = true;
@@ -370,26 +492,34 @@ public class SurfaceSyncGroup {
// 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.mName
- + " but already part of sync group " + mParentSyncGroup.mName + " "
+ Log.d(TAG, "Trying to add to " + parentSyncGroup
+ + " but already part of sync group " + mParentSyncGroup + " "
+ mName);
}
- parentSyncGroup.addToSync(mParentSyncGroup, true /* parentSyncGroupMerge */);
+ try {
+ parentSyncGroup.addToSync(mParentSyncGroup,
+ true /* parentSyncGroupMerge */);
+ } catch (RemoteException e) {
+ }
}
- if (mParentSyncGroup == parentSyncGroup) {
- if (DEBUG) {
- Log.d(TAG, "Added to parent that was already the parent");
- }
+ if (DEBUG && mParentSyncGroup == parentSyncGroup) {
+ Log.d(TAG, "Added to parent that was already the parent");
}
+
+ Consumer<Transaction> lastCallback = mTransactionReadyConsumer;
mParentSyncGroup = parentSyncGroup;
- final TransactionReadyCallback lastCallback = mTransactionReadyCallback;
- mTransactionReadyCallback = t -> {
+ mTransactionReadyConsumer = (transaction) -> {
Trace.traceBegin(Trace.TRACE_TAG_VIEW,
- "transactionReadyCallback " + mName + " parent="
- + parentSyncGroup.mName);
- lastCallback.onTransactionReady(null);
- transactionReadyCallback.onTransactionReady(t);
+ "transactionReadyCallback " + mName + " callback="
+ + transactionReadyCallback.hashCode());
+ lastCallback.accept(null);
+
+ try {
+ transactionReadyCallback.onTransactionReady(transaction);
+ } catch (RemoteException e) {
+ transaction.apply();
+ }
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
};
}
@@ -398,7 +528,12 @@ public class SurfaceSyncGroup {
// Invoke the callback outside of the lock when the SurfaceSyncGroup being added was already
// complete.
if (finished) {
- transactionReadyCallback.onTransactionReady(null);
+ try {
+ transactionReadyCallback.onTransactionReady(null);
+ } catch (RemoteException e) {
+ }
+ } else {
+ onSyncReady();
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
@@ -407,19 +542,89 @@ public class SurfaceSyncGroup {
return mName;
}
+ @GuardedBy("mLock")
+ private void checkIfSyncIsComplete() {
+ if (mFinished) {
+ if (DEBUG) {
+ Log.d(TAG, "SurfaceSyncGroup=" + mName + " is already complete");
+ }
+ mTransaction.apply();
+ return;
+ }
+
+ Trace.instant(Trace.TRACE_TAG_VIEW,
+ "checkIfSyncIsComplete " + mName + " 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;
+ }
+
/**
- * Interface so the SurfaceSyncer can know when it's safe to start and when everything has been
- * completed. The caller should invoke the calls when the rendering has started and finished a
- * frame.
+ * Create an {@link ITransactionReadyCallback} that the current SurfaceSyncGroup will wait on
+ * before completing. The caller must ensure that the
+ * {@link ITransactionReadyCallback#onTransactionReady(Transaction)} 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.
*/
- private interface TransactionReadyCallback {
- /**
- * Invoked when the transaction is ready to sync.
- *
- * @param t The transaction that contains the anything to be included in the synced. This
- * can be null if there's nothing to sync
- */
- void onTransactionReady(@Nullable Transaction t);
+ public ITransactionReadyCallback createTransactionReadyCallback(boolean parentSyncGroupMerge) {
+ if (DEBUG) {
+ Log.d(TAG, "createTransactionReadyCallback " + mName);
+ }
+ ITransactionReadyCallback transactionReadyCallback =
+ new ITransactionReadyCallback.Stub() {
+ @Override
+ public void onTransactionReady(Transaction t) {
+ synchronized (mLock) {
+ if (t != null) {
+ // 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);
+ Trace.instant(Trace.TRACE_TAG_VIEW,
+ "onTransactionReady group=" + mName + " 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);
+ Trace.instant(Trace.TRACE_TAG_VIEW,
+ "createTransactionReadyCallback " + mName + " mPendingSyncs="
+ + mPendingSyncs.size() + " transactionReady="
+ + transactionReadyCallback.hashCode());
+ }
+
+ return transactionReadyCallback;
}
/**
diff --git a/core/java/android/window/SurfaceSyncGroup.md b/core/java/android/window/SurfaceSyncGroup.md
index b4faeac57e58..406c230ad920 100644
--- a/core/java/android/window/SurfaceSyncGroup.md
+++ b/core/java/android/window/SurfaceSyncGroup.md
@@ -2,7 +2,7 @@
### Overview
-A generic way for data to be gathered so multiple surfaces can be synced. This is intended to be used with Views, SurfaceView, and any other surface that wants to be involved in a sync. This allows different parts of the Android system to synchronize different windows and layers themselves without having to go through WindowManagerService
+A generic way for data to be gathered so multiple surfaces can be synced. This is intended to be used with Views, SurfaceView, and any other surface that wants to be involved in a sync. This allows different parts of the Android system to synchronize different windows and layers themselves.
### Code
@@ -12,46 +12,46 @@ SurfaceSyncGroup is a class that manages sync requests and reports back when all
The first step is to create a sync request. This is done by creating a new `SurfaceSyncGroup`.
There are two constructors: one that accepts a `Consumer<Transaction>` and one that's empty. The empty constructor will automatically apply the final transaction. The second constructor should only be used by ViewRootImpl. The purpose of this one is to allow the caller to get back the merged transaction without it being applied. ViewRootImpl uses it to send the transaction to WindowManagerService to be synced there. Using this one for other cases is unsafe because the caller may hold the transaction longer than expected and prevent buffers from being latched and released.
-##### markSyncReady
-
-When the caller has added all the `SyncTarget` to the sync, they should call `markSyncReady()` If the caller doesn't call this, the sync will never complete since the SurfaceSyncGroup wants to give the caller a chance to add all the SyncTargets before considering the sync ready. Before `markSyncReady` is called, the `SyncTargets` can actually produce a frame, which will just be held in a transaction until all other `SyncTargets` are ready AND `markSyncReady` has been called. Once markSyncReady has been called, you cannot add any more `SyncTargets` to that particular SurfaceSyncGroup.
-
##### addToSync
-The caller will invoke `addToSync` for every `SyncTarget` that it wants included. There are a few helper methods since the most common cases are Views and SurfaceView
+The caller will invoke `addToSync` for every `SurfaceSyncGroup` that it wants included. There are a few helper methods since the most common cases are Views and SurfaceView
* `addToSync(AttachedSurfaceControl)` - This is used for syncing the root of the View, specificially the ViewRootImpl
* `addToSync(SurfaceView, Consumer<SurfaceViewFrameCallback>)` - This is to sync a SurfaceView. Since SurfaceViews are rendered by the app, the caller will be expected to provide a way to get back the buffer to sync. More details about that [below](#surfaceviewframecallback)
-* `addToSync(SyncTarget)` - This is the generic method. It can be used to sync arbitrary info. The SyncTarget interface has required methods that need to be implemented to properly get the transaction to sync.
+* `addToSync(SurfaceControlViewHost.SurfacePackage)` - This is to sync an embedded window. The host can call addToSync and pass in the SurfacePackage, where the SurfaceSyncGroup will ensure it's added to the sync.
+* `addToSync(ISurfaceSyncGroup)` - This is the generic method. It can be used to sync arbitrary info. Most likely the caller will pass in a SurfaceSyncGroup object and then they are responsible for calling markSyncReady for the child SurfaceSyncGroup.
When calling addToSync with either AttachedSurfaceControl or SurfaceView, it must be called on the UI Thread. This is to ensure consistent behavior, where any UI changes done while still on the UI thread are included in this frame. The next vsync will pick up those changes and request to draw.
-##### addTransactionToSync
+An additional Runnable argument can be passed in which ensures the Runnable has executed before adding the child SurfaceSyncGroup to the parent. The purpose of this Runnable is to execute any changes the caller wants and they are guaranteed to be picked up in the sync.
-This is a simple method that allows callers to add generic Transactions to the sync. The caller invokes `addTransactionToSync(Transaction)`. This can be used for any additional things that need to be included in the same SyncGroup.
+##### markSyncReady
-##### merge
+When the caller has added all the `SurfaceSyncGroup` to the sync, they should call `markSyncReady()` If the caller doesn't call this, the sync will never complete since the SurfaceSyncGroup wants to give the caller a chance to add SurfaceSyncGroups before considering the sync ready. Before `markSyncReady` is called, the `SurfaceSyncGroups` can actually produce a frame, which will just be held in a transaction until all other `SurfaceSyncGroup` are ready AND `markSyncReady` has been called. Once markSyncReady has been called, you cannot add any more `SurfaceSyncGroup` to that particular SurfaceSyncGroup.
-To add more flexibility to Syncs, an API is provided to merge SurfaceSyncGroups. The caller provides the SurfaceSyncGroup it wants merged. The current SurfaceSyncGroup will now wait for the passed in SurfaceSyncGroup to complete, as well as its own SyncTargets to complete before invoking the callback. The passed in SurfaceSyncGroup will also get a complete callback but only when its SurfaceSyncGroup completes, not the one it merged into. If a `Consumer<Transaction>` was passed in to the SurfaceSyncGroup, it will get back an emtpy Transaction so it can't accidentally apply things that were meant to be merged.
+##### addTransactionToSync
+
+This is a simple method that allows callers to add generic Transactions to the sync. The caller invokes `addTransactionToSync(Transaction)`. This can be used for any additional things that need to be included in the same SyncGroup.
##### addSyncCompleteCallback
-This allows callers to receive a callback when the sync is complete. The method takes in an Executor and a Runnable that will be invoked when the SurfaceSyncGroup has completed. The Executor is used to invoke the callback on the desired thread. You can add more than one callback.
+This allows callers to receive a callback when the sync is complete. The caller will only receive a complete callback when the specific SurfaceSyncGroup it registered with is complete. This means that the SurfaceSyncGroup has been marked as ready and all children are complete. This doesn't mean the transaction has been applied since it could be passed to a parent SurfaceSyncGroup or passed to another process. This can be helpful if the caller wants to know that all the children have rendered their frame and possibly to ensure they can pace. The method takes in an Executor and a Runnable that will be invoked when the SurfaceSyncGroup has completed. The Executor is used to invoke the callback on the desired thread. You can add more than one callback.
-##### SyncTarget
+##### SurfaceViewFrameCallback
-This interface is used to handle syncs. The interface has two methods
-* `onReadyToSync(SyncBufferCallback)` - This one must be implemented. The sync will notify the `SyncTarget` so it can invoke the `SyncBufferCallback`, letting the sync know when it's ready.
-* `onSyncComplete()` - This method is optional. It's used to notify the `SyncTarget` that the entire sync is complete. This is similar to the callback sent in `setupSync`, but it's invoked to the `SyncTargets` rather than the caller who started the sync. This is used by ViewRootImpl to restore the state when the entire sync is done
+As mentioned above, SurfaceViews are a special case because the buffers produced are handled by the app, and not the framework. Because of this, the SurfaceSyncGroup doesn't know which frame to sync. Therefore, to sync SurfaceViews, the caller must provide a way to notify the SurfaceSyncGroup that it's going to render a buffer and that this next buffer is the one to sync. The `SurfaceViewFrameCallback` has one method `onFrameStarted()`. When this is invoked, the SurfaceSyncGroup sets up a request to sync the next buffer for the SurfaceView.
-When syncing ViewRootImpl, these methods are implemented already since ViewRootImpl handles the rendering requests and timing.
+#### ViewRootImpl's SurfaceSyncGroup
-##### SyncBufferCallback
+The most common way to use SurfaceSyncGroups is to sync multiple ViewRootImpls. The framework handles the timing and rendering of the ViewRootImpl, so it's expected that the caller doesn't need to know about this to ensure it's properly synced. This is done by the following flow.
-This interface is used to tell the sync that this SyncTarget is ready. There's only method here, `onBufferReady(Transaction)`, that sends back the transaction that contains the data to be synced, normally with a buffer.
+When ViewRootImpl is added to a SurfaceSyncGroup, it's done so via `addToSync(AttachedSurfaceControl)`. The SurfaceSyncGroup code will call into ViewRootImpl, where either a new SurfaceSyncGroup is created or it returns an already active SurfaceSyncGroup. This active SurfaceSyncGroup is stored in ViewRootImpl and is tied to a rendering cycle. If multiple syncs are requested before VRI draws, they will all get back the same SurfaceSyncGroup object. This is to ensure we can make multiple changes without having to queue up several frames and causing things to get backed up. For example, if SurfaceSyncGroup1 wants to resize VRI and then SurfaceSyncGroup2 wants to change an attribute, if they are both done before a frame is drawn, then both those changes are picked up together. Once VRI starts a draw pass, it will clear the reference to the active SurfaceSyncGroup so any new changes that want to be synced are done with a new SurfaceSyncGroup object. The old active SurfaceSyncGroup is passed via lambda when the rendering is occuring. Once RenderThread completes the frame, VRI calls `SurfaceSyncGroup.addTransactionToSync(Transaction)`, passing in the transaction that contains the buffer and then `SurfaceSyncGroup.markSyncReady()` so the VRI SurfaceSyncGroup knows it's complete. This way we can start additional syncs without having to wait for the RenderThread to complete.
-##### SurfaceViewFrameCallback
+#### SurfaceSyncGroup added to Multiple SurfaceSyncGroups
+There are cases where multiple places are trying to sync the same object. This is more common with VRI where you may have multiple changes for the same vsync rendering cycle, but different places requested the sync. Because each SurfaceSyncGroup may also contain other SurfaceSyncGroups, we need to combine everything. So for example, if SurfaceSyncGroup1 wants to sync VRI and SurfaceSyncGroup2 wants to sync VRI, but SSG-1 already contains SSG-A and SSG-2 already contains SSG-B, we need to ensure SSG-A, SSG-B, and VRI are all applied together to ensure the contract is held. This is done by creating a tree like structure and merging parents when needed. VRI will be added to SSG-1. Then SSG-2 also requests to sync VRI. VRI's SSG will see that it already is part of a SSG and keeps track of its last parent. The SSG will call newParentSyncGroup.add(oldParentSyncGroup). VRI is now added to both SSG-1 and SSG-2, but will only send it's transaction to SSG-2 (the one it was added to last). SSG-1 is also now added to SSG-2 so it will not apply it's transaction, but send it to SSG-2 to apply. SSG-2 will end up waiting for SSG-1 which is waiting for SSG-A so we know that SSG-2 will have the final transaction that includes everything from SSG-1 and its own children.
-As mentioned above, SurfaceViews are a special case because the buffers produced are handled by the app, and not the framework. Because of this, the SurfaceSyncGroup doesn't know which frame to sync. Therefore, to sync SurfaceViews, the caller must provide a way to notify the SurfaceSyncGroup that it's going to render a buffer and that this next buffer is the one to sync. The `SurfaceViewFrameCallback` has one method `onFrameStarted()`. When this is invoked, the SurfaceSyncGroup sets up a request to sync the next buffer for the SurfaceView.
+#### WindowManagerService Involvement
+
+The above works fine when everything is in process. However, we don't want to expose transactions that are tied to SurfaceControls to other processes. This is because they can inject any call if they have a way to get the SurfaceControl object. Instead, use WMS as an intermediary when trying to sync cross-process. The APIs from the client perspectives don't change and the callers don't have to worry about the implementation. This is all done via the SurfaceSyncGroup code. When SurfaceSyncGroup recognizes that a process is trying to add another SurfaceSyncGroup from a different process, it will not be able to add it locally. Instead, it will call into WMS and register a new SurfaceSyncGroup that will be managed by WMS. It then notifies the other process that it should add itself to this SSG that was created in WMS. The other process will also call into WMS and add its own SSG. WMS is now the parent of both SSG from different processes. This means both processes will send their transaction to WMS, which is secure. When the original SSG calls markSyncReady, it will also mark the SSG in WMS as ready so the WMS created SSG can apply the transaction when all children have completed.
### Example
@@ -70,6 +70,7 @@ syncGroup.markSyncReady();
```
A SurfaceView example:
+
See `frameworks/base/tests/SurfaceViewSyncTest` for a working example
```java
@@ -83,4 +84,26 @@ syncGroup.addToSync(surfaceView, frameCallback -> {
frameCallback.onFrameStarted()
}
syncGroup.markSyncReady();
+```
+
+A SurfaceControlViewHost example (also an cross process example):
+
+See `frameworks/base/tests/SurfaceControlViewHostTest/.../SurfaceControlViewHostSyncTest.java` for working example
+
+This would sync both the view resize and the embedded resize in the same frame
+```java
+SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(NAME);
+SyncGroup.addSyncCompleteCallback(mMainThreadExecutor, () -> {
+ Log.d(TAG, "syncComplete");
+};
+syncGroup.addToSync(getWindow().getRootSurfaceControl(), () -> {
+ view.getLayoutParams().width = 20;
+ view.getLayoutParams().height = 40;
+ view.requestLayout();
+});
+syncGroup.addToSync(mSurfacePackage, () -> {
+ // Side channel API that's decided between the two processes
+ mEmbedded.resize(20, 40);
+});
+syncGroup.markSyncReady();
``` \ No newline at end of file
diff --git a/services/core/java/com/android/server/wm/SurfaceSyncGroupController.java b/services/core/java/com/android/server/wm/SurfaceSyncGroupController.java
new file mode 100644
index 000000000000..75691caad246
--- /dev/null
+++ b/services/core/java/com/android/server/wm/SurfaceSyncGroupController.java
@@ -0,0 +1,101 @@
+/*
+ * 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 com.android.server.wm;
+
+import android.annotation.Nullable;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.window.AddToSurfaceSyncGroupResult;
+import android.window.ISurfaceSyncGroupCompletedListener;
+import android.window.ITransactionReadyCallback;
+import android.window.SurfaceSyncGroup;
+
+import com.android.internal.annotations.GuardedBy;
+
+class SurfaceSyncGroupController {
+ private static final String TAG = "SurfaceSyncGroupController";
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private final ArrayMap<IBinder, SurfaceSyncGroupData> mSurfaceSyncGroups = new ArrayMap<>();
+
+ boolean addToSyncGroup(IBinder syncGroupToken, boolean parentSyncGroupMerge,
+ @Nullable ISurfaceSyncGroupCompletedListener completedListener,
+ AddToSurfaceSyncGroupResult outAddToSyncGroupResult) {
+ SurfaceSyncGroup root;
+ synchronized (mLock) {
+ SurfaceSyncGroupData syncGroupData = mSurfaceSyncGroups.get(syncGroupToken);
+ if (syncGroupData == null) {
+ root = new SurfaceSyncGroup(TAG + "-" + syncGroupToken.hashCode());
+ if (completedListener != null) {
+ root.addSyncCompleteCallback(Runnable::run, () -> {
+ try {
+ completedListener.onSurfaceSyncGroupComplete();
+ } catch (RemoteException e) {
+ }
+ });
+ }
+ mSurfaceSyncGroups.put(syncGroupToken,
+ new SurfaceSyncGroupData(Binder.getCallingUid(), root));
+ } else {
+ root = syncGroupData.mSurfaceSyncGroup;
+ }
+ }
+
+ ITransactionReadyCallback callback =
+ root.createTransactionReadyCallback(parentSyncGroupMerge);
+ if (callback == null) {
+ return false;
+ }
+ outAddToSyncGroupResult.mParentSyncGroup = root;
+ outAddToSyncGroupResult.mTransactionReadyCallback = callback;
+ return true;
+ }
+
+ void markSyncGroupReady(IBinder syncGroupToken) {
+ final SurfaceSyncGroup root;
+ synchronized (mLock) {
+ SurfaceSyncGroupData syncGroupData = mSurfaceSyncGroups.get(syncGroupToken);
+ if (syncGroupData == null) {
+ throw new IllegalArgumentException(
+ "SurfaceSyncGroup Token has not been set up or has already been marked as"
+ + " ready");
+ }
+ if (syncGroupData.mOwningUid != Binder.getCallingUid()) {
+ throw new IllegalArgumentException(
+ "Only process that created the SurfaceSyncGroup can call "
+ + "markSyncGroupReady");
+ }
+ root = syncGroupData.mSurfaceSyncGroup;
+ mSurfaceSyncGroups.remove(syncGroupToken);
+ }
+
+ root.markSyncReady();
+ }
+
+ private static class SurfaceSyncGroupData {
+ final int mOwningUid;
+ final SurfaceSyncGroup mSurfaceSyncGroup;
+
+ private SurfaceSyncGroupData(int owningUid, SurfaceSyncGroup surfaceSyncGroup) {
+ mOwningUid = owningUid;
+ mSurfaceSyncGroup = surfaceSyncGroup;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index b83f4231bd78..89c862bb1d6e 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -292,7 +292,9 @@ import android.view.WindowManagerPolicyConstants.PointerEventListener;
import android.view.displayhash.DisplayHash;
import android.view.displayhash.VerifiedDisplayHash;
import android.view.inputmethod.ImeTracker;
+import android.window.AddToSurfaceSyncGroupResult;
import android.window.ClientWindowFrames;
+import android.window.ISurfaceSyncGroupCompletedListener;
import android.window.ITaskFpsCallback;
import android.window.ScreenCapture;
import android.window.TaskSnapshot;
@@ -746,6 +748,10 @@ public class WindowManagerService extends IWindowManager.Stub
@VisibleForTesting
final ContentRecordingController mContentRecordingController = new ContentRecordingController();
+
+ private final SurfaceSyncGroupController mSurfaceSyncGroupController =
+ new SurfaceSyncGroupController();
+
@VisibleForTesting
final class SettingsObserver extends ContentObserver {
private final Uri mDisplayInversionEnabledUri =
@@ -9408,4 +9414,16 @@ public class WindowManagerService extends IWindowManager.Stub
}
return type;
}
+ @Override
+ public boolean addToSurfaceSyncGroup(IBinder syncGroupToken, boolean parentSyncGroupMerge,
+ @Nullable ISurfaceSyncGroupCompletedListener completedListener,
+ AddToSurfaceSyncGroupResult outAddToSyncGroupResult) {
+ return mSurfaceSyncGroupController.addToSyncGroup(syncGroupToken, parentSyncGroupMerge,
+ completedListener, outAddToSyncGroupResult);
+ }
+
+ @Override
+ public void markSurfaceSyncGroupReady(IBinder syncGroupToken) {
+ mSurfaceSyncGroupController.markSyncGroupReady(syncGroupToken);
+ }
}
diff --git a/services/tests/wmtests/Android.bp b/services/tests/wmtests/Android.bp
index 079d765868fd..91226fa786b5 100644
--- a/services/tests/wmtests/Android.bp
+++ b/services/tests/wmtests/Android.bp
@@ -27,13 +27,13 @@ genrule {
],
tools: ["protologtool"],
cmd: "$(location protologtool) transform-protolog-calls " +
- "--protolog-class com.android.internal.protolog.common.ProtoLog " +
- "--protolog-impl-class com.android.internal.protolog.ProtoLogImpl " +
- "--protolog-cache-class 'com.android.server.wm.ProtoLogCache' " +
- "--loggroups-class com.android.internal.protolog.ProtoLogGroup " +
- "--loggroups-jar $(location :protolog-groups) " +
- "--output-srcjar $(out) " +
- "$(locations :wmtests-sources)",
+ "--protolog-class com.android.internal.protolog.common.ProtoLog " +
+ "--protolog-impl-class com.android.internal.protolog.ProtoLogImpl " +
+ "--protolog-cache-class 'com.android.server.wm.ProtoLogCache' " +
+ "--loggroups-class com.android.internal.protolog.ProtoLogGroup " +
+ "--loggroups-jar $(location :protolog-groups) " +
+ "--output-srcjar $(out) " +
+ "$(locations :wmtests-sources)",
out: ["wmtests.protolog.srcjar"],
}
@@ -41,7 +41,10 @@ android_test {
name: "WmTests",
// We only want this apk build for tests.
- srcs: [":wmtests.protologsrc"],
+ srcs: [
+ ":wmtests.protologsrc",
+ "src/**/*.aidl",
+ ],
static_libs: [
"frameworks-base-testutils",
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index 593ee4a7fa1a..b51feb3ac5c7 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -90,6 +90,9 @@
android:foregroundServiceType="mediaProjection"
android:enabled="true">
</service>
+
+ <service android:name="com.android.server.wm.scvh.EmbeddedSCVHService"
+ android:process="com.android.server.wm.scvh.embedded_process" />
</application>
<instrumentation
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupContinuousTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupContinuousTest.java
index 8c49c26f3802..e6f47a1a493e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupContinuousTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupContinuousTest.java
@@ -28,6 +28,8 @@ import android.window.SurfaceSyncGroup;
import androidx.test.rule.ActivityTestRule;
+import com.android.server.wm.scvh.SyncValidatorSCVHTestCase;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -56,11 +58,21 @@ public class SurfaceSyncGroupContinuousTest {
pressWakeupButton();
pressUnlockButton();
}
+ SurfaceSyncGroup.setTransactionFactory(SurfaceControl.Transaction::new);
}
@Test
public void testSurfaceViewSyncDuringResize() throws Throwable {
- SurfaceSyncGroup.setTransactionFactory(SurfaceControl.Transaction::new);
mCapturedActivity.verifyTest(new SurfaceSyncGroupValidatorTestCase(), mName);
}
+
+ @Test
+ public void testSurfaceControlViewHostIPCSync_Fast() throws Throwable {
+ mCapturedActivity.verifyTest(new SyncValidatorSCVHTestCase(0 /* delayMs */), mName);
+ }
+
+ @Test
+ public void testSurfaceControlViewHostIPCSync_Slow() throws Throwable {
+ mCapturedActivity.verifyTest(new SyncValidatorSCVHTestCase(100 /* delayMs */), mName);
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
index d2cca9f3644e..f655242afac8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
@@ -56,7 +56,7 @@ public class SurfaceSyncGroupTest {
syncGroup.addSyncCompleteCallback(mExecutor, finishedLatch::countDown);
SyncTarget syncTarget = new SyncTarget();
syncGroup.addToSync(syncTarget, false /* parentSyncGroupMerge */);
- syncGroup.onTransactionReady(null);
+ syncGroup.markSyncReady();
syncTarget.onBufferReady();
@@ -76,7 +76,7 @@ public class SurfaceSyncGroupTest {
syncGroup.addToSync(syncTarget1, false /* parentSyncGroupMerge */);
syncGroup.addToSync(syncTarget2, false /* parentSyncGroupMerge */);
syncGroup.addToSync(syncTarget3, false /* parentSyncGroupMerge */);
- syncGroup.onTransactionReady(null);
+ syncGroup.markSyncReady();
syncTarget1.onBufferReady();
assertNotEquals(0, finishedLatch.getCount());
@@ -98,7 +98,7 @@ public class SurfaceSyncGroupTest {
SyncTarget syncTarget2 = new SyncTarget();
assertTrue(syncGroup.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
- syncGroup.onTransactionReady(null);
+ syncGroup.markSyncReady();
// Adding to a sync that has been completed is also invalid since the sync id has been
// cleared.
assertFalse(syncGroup.addToSync(syncTarget2, false /* parentSyncGroupMerge */));
@@ -119,8 +119,8 @@ public class SurfaceSyncGroupTest {
assertTrue(syncGroup1.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
assertTrue(syncGroup2.addToSync(syncTarget2, false /* parentSyncGroupMerge */));
- syncGroup1.onTransactionReady(null);
- syncGroup2.onTransactionReady(null);
+ syncGroup1.markSyncReady();
+ syncGroup2.markSyncReady();
syncTarget1.onBufferReady();
@@ -149,9 +149,9 @@ public class SurfaceSyncGroupTest {
assertTrue(syncGroup1.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
assertTrue(syncGroup2.addToSync(syncTarget2, false /* parentSyncGroupMerge */));
- syncGroup1.onTransactionReady(null);
+ syncGroup1.markSyncReady();
syncGroup2.addToSync(syncGroup1, false /* parentSyncGroupMerge */);
- syncGroup2.onTransactionReady(null);
+ syncGroup2.markSyncReady();
// Finish syncTarget2 first to test that the syncGroup is not complete until the merged sync
// is also done.
@@ -185,7 +185,7 @@ public class SurfaceSyncGroupTest {
assertTrue(syncGroup1.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
assertTrue(syncGroup2.addToSync(syncTarget2, false /* parentSyncGroupMerge */));
- syncGroup1.onTransactionReady(null);
+ syncGroup1.markSyncReady();
syncTarget1.onBufferReady();
// The first sync will still get a callback when it's sync requirements are done.
@@ -193,7 +193,7 @@ public class SurfaceSyncGroupTest {
assertEquals(0, finishedLatch1.getCount());
syncGroup2.addToSync(syncGroup1, false /* parentSyncGroupMerge */);
- syncGroup2.onTransactionReady(null);
+ syncGroup2.markSyncReady();
syncTarget2.onBufferReady();
// Verify that the second sync will receive complete since the merged sync was already
@@ -223,8 +223,8 @@ public class SurfaceSyncGroupTest {
assertTrue(syncGroup2.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
assertTrue(syncGroup2.addToSync(syncTarget3, false /* parentSyncGroupMerge */));
- syncGroup1.onTransactionReady(null);
- syncGroup2.onTransactionReady(null);
+ syncGroup1.markSyncReady();
+ syncGroup2.markSyncReady();
// Make target1 and target3 ready, but not target2. SyncGroup2 should not be ready since
// SyncGroup2 also waits for all of SyncGroup1 to finish, which includes target2
@@ -269,8 +269,8 @@ public class SurfaceSyncGroupTest {
assertTrue(syncGroup2.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
assertTrue(syncGroup2.addToSync(syncTarget3, false /* parentSyncGroupMerge */));
- syncGroup1.onTransactionReady(null);
- syncGroup2.onTransactionReady(null);
+ syncGroup1.markSyncReady();
+ syncGroup2.markSyncReady();
syncTarget1.onBufferReady();
@@ -304,7 +304,7 @@ public class SurfaceSyncGroupTest {
SyncTarget syncTarget = new SyncTarget();
assertTrue(syncGroup.addToSync(syncTarget, true /* parentSyncGroupMerge */));
- syncTarget.onTransactionReady(null);
+ syncTarget.markSyncReady();
// When parentSyncGroupMerge is true, the transaction passed in merges the main SyncGroup
// transaction first because it knows the previous parentSyncGroup is older so it should
@@ -329,7 +329,7 @@ public class SurfaceSyncGroupTest {
SyncTarget syncTarget = new SyncTarget();
assertTrue(syncGroup.addToSync(syncTarget, false /* parentSyncGroupMerge */));
- syncTarget.onTransactionReady(null);
+ syncTarget.markSyncReady();
// When parentSyncGroupMerge is false, the transaction passed in should not merge
// the main SyncGroup since we don't need to change the transaction order
@@ -346,7 +346,7 @@ public class SurfaceSyncGroupTest {
syncGroup.addToSync(syncTarget, false /* parentSyncGroupMerge */);
// Add the syncTarget to the same syncGroup and ensure it doesn't crash.
syncGroup.addToSync(syncTarget, false /* parentSyncGroupMerge */);
- syncGroup.onTransactionReady(null);
+ syncGroup.markSyncReady();
syncTarget.onBufferReady();
@@ -364,8 +364,7 @@ public class SurfaceSyncGroupTest {
}
void onBufferReady() {
- SurfaceControl.Transaction t = new StubTransaction();
- onTransactionReady(t);
+ markSyncReady();
}
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/scvh/EmbeddedSCVHService.java b/services/tests/wmtests/src/com/android/server/wm/scvh/EmbeddedSCVHService.java
new file mode 100644
index 000000000000..3bd577cb1d64
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/scvh/EmbeddedSCVHService.java
@@ -0,0 +1,127 @@
+/*
+ * 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 com.android.server.wm.scvh;
+
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+
+import android.annotation.Nullable;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.hardware.display.DisplayManager;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.view.Display;
+import android.view.SurfaceControl.Transaction;
+import android.view.SurfaceControlViewHost;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+import androidx.annotation.NonNull;
+
+public class EmbeddedSCVHService extends Service {
+ private static final String TAG = "SCVHEmbeddedService";
+ private SurfaceControlViewHost mVr;
+
+ private Handler mHandler;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mHandler = new Handler(Looper.getMainLooper());
+ }
+
+ @Nullable
+ @Override
+ public IBinder onBind(Intent intent) {
+ // Return the interface
+ return new AttachEmbeddedWindow();
+ }
+
+ public static class SlowView extends View {
+ private long mDelayMs;
+ public SlowView(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ try {
+ Thread.sleep(mDelayMs);
+ } catch (InterruptedException e) {
+ }
+ }
+
+ public void setDelay(long delayMs) {
+ mDelayMs = delayMs;
+ }
+ }
+
+ private class AttachEmbeddedWindow extends IAttachEmbeddedWindow.Stub {
+ @Override
+ public void attachEmbedded(IBinder hostToken, int width,
+ int height, int displayId, long delayMs, IAttachEmbeddedWindowCallback callback) {
+ mHandler.post(() -> {
+ Context context = EmbeddedSCVHService.this;
+ Display display = getApplicationContext().getSystemService(
+ DisplayManager.class).getDisplay(displayId);
+ mVr = new SurfaceControlViewHost(context, display, hostToken);
+ FrameLayout content = new FrameLayout(context);
+
+ SlowView slowView = new SlowView(context);
+ slowView.setDelay(delayMs);
+ slowView.setBackgroundColor(Color.BLUE);
+ content.addView(slowView);
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams(width, height,
+ TYPE_APPLICATION, 0, PixelFormat.OPAQUE);
+ lp.setTitle("EmbeddedWindow");
+ mVr.setView(content, lp);
+
+ content.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(@NonNull View v) {
+ // First frame isn't included in the sync so don't notify the host about the
+ // surface package until the first draw has completed.
+ Transaction transaction = new Transaction().addTransactionCommittedListener(
+ getMainExecutor(), () -> {
+ try {
+ callback.onEmbeddedWindowAttached(mVr.getSurfacePackage());
+ } catch (RemoteException e) {
+ }
+ });
+ v.getRootSurfaceControl().applyTransactionOnDraw(transaction);
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(@NonNull View v) {
+ }
+ });
+ });
+ }
+
+ @Override
+ public void relayout(WindowManager.LayoutParams lp) {
+ mHandler.post(() -> mVr.relayout(lp));
+ }
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/scvh/IAttachEmbeddedWindow.aidl b/services/tests/wmtests/src/com/android/server/wm/scvh/IAttachEmbeddedWindow.aidl
new file mode 100644
index 000000000000..343956759f09
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/scvh/IAttachEmbeddedWindow.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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 com.android.server.wm.scvh;
+
+import android.view.SurfaceControlViewHost.SurfacePackage;
+import android.os.IBinder;
+import com.android.server.wm.scvh.IAttachEmbeddedWindowCallback;
+import android.view.WindowManager.LayoutParams;
+
+interface IAttachEmbeddedWindow {
+ void attachEmbedded(IBinder hostToken, int width, int height, int displayId, long delayMs, IAttachEmbeddedWindowCallback callback);
+ void relayout(in LayoutParams lp);
+} \ No newline at end of file
diff --git a/services/tests/wmtests/src/com/android/server/wm/scvh/IAttachEmbeddedWindowCallback.aidl b/services/tests/wmtests/src/com/android/server/wm/scvh/IAttachEmbeddedWindowCallback.aidl
new file mode 100644
index 000000000000..92abfc8eea24
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/scvh/IAttachEmbeddedWindowCallback.aidl
@@ -0,0 +1,23 @@
+/*
+ * 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 com.android.server.wm.scvh;
+
+import android.view.SurfaceControlViewHost.SurfacePackage;
+
+interface IAttachEmbeddedWindowCallback {
+ void onEmbeddedWindowAttached(in SurfacePackage surfacePackage);
+} \ No newline at end of file
diff --git a/services/tests/wmtests/src/com/android/server/wm/scvh/SyncValidatorSCVHTestCase.java b/services/tests/wmtests/src/com/android/server/wm/scvh/SyncValidatorSCVHTestCase.java
new file mode 100644
index 000000000000..af4c683ca5e6
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/scvh/SyncValidatorSCVHTestCase.java
@@ -0,0 +1,234 @@
+/*
+ * 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 com.android.server.wm.scvh;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.SurfaceControlViewHost.SurfacePackage;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.cts.surfacevalidator.ISurfaceValidatorTestCase;
+import android.view.cts.surfacevalidator.PixelChecker;
+import android.widget.FrameLayout;
+import android.window.SurfaceSyncGroup;
+
+import androidx.annotation.NonNull;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class SyncValidatorSCVHTestCase implements ISurfaceValidatorTestCase {
+ private static final String TAG = "SCVHSyncValidatorTestCase";
+
+ private final Point[] mSizes = new Point[]{new Point(500, 500), new Point(700, 400),
+ new Point(300, 800), new Point(200, 200)};
+ private int mLastSizeIndex = 1;
+
+ private long mDelayMs;
+
+ public SyncValidatorSCVHTestCase(long delayMs) {
+ mDelayMs = delayMs;
+ }
+
+ private final Runnable mRunnable = new Runnable() {
+ @Override
+ public void run() {
+ Point size = mSizes[mLastSizeIndex % mSizes.length];
+ Runnable svResizeRunnable = () -> {
+ ViewGroup.LayoutParams svParams = mSurfaceView.getLayoutParams();
+ svParams.width = size.x;
+ svParams.height = size.y;
+ mSurfaceView.setLayoutParams(svParams);
+ };
+ Runnable resizeRunnable = () -> {
+ try {
+ final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(size.x,
+ size.y,
+ WindowManager.LayoutParams.TYPE_APPLICATION, 0,
+ PixelFormat.TRANSPARENT);
+ mIAttachEmbeddedWindow.relayout(lp);
+ } catch (RemoteException e) {
+ }
+ };
+
+ SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(TAG);
+ syncGroup.addToSync(mSurfaceView.getRootSurfaceControl(), svResizeRunnable);
+ syncGroup.addToSync(mSurfacePackage, resizeRunnable);
+ syncGroup.markSyncReady();
+
+ mLastSizeIndex++;
+
+ mHandler.postDelayed(this, mDelayMs + 50);
+ }
+ };
+
+ private Handler mHandler;
+ private SurfaceView mSurfaceView;
+
+ private final CountDownLatch mReadyLatch = new CountDownLatch(1);
+ private boolean mSurfaceCreated;
+ private boolean mIsAttached;
+ private final Object mLock = new Object();
+ private int mDisplayId;
+ private IAttachEmbeddedWindow mIAttachEmbeddedWindow;
+ private SurfacePackage mSurfacePackage;
+
+ final SurfaceHolder.Callback mCallback = new SurfaceHolder.Callback() {
+ @Override
+ public void surfaceCreated(@NonNull SurfaceHolder holder) {
+ synchronized (mLock) {
+ mSurfaceCreated = true;
+ }
+ if (isReadyToAttach()) {
+ attachEmbedded();
+ }
+ }
+
+ @Override
+ public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width,
+ int height) {
+ }
+
+ @Override
+ public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
+ }
+ };
+
+ @Override
+ public PixelChecker getChecker() {
+ return new PixelChecker(Color.BLACK) {
+ @Override
+ public boolean checkPixels(int matchingPixelCount, int width, int height) {
+ // Content has been set up yet.
+ if (mReadyLatch.getCount() > 0) {
+ return true;
+ }
+ return matchingPixelCount == 0;
+ }
+ };
+ }
+
+ private final ServiceConnection mConnection = new ServiceConnection() {
+ // Called when the connection with the service is established
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ Log.d(TAG, "Service Connected");
+ synchronized (mLock) {
+ mIAttachEmbeddedWindow = IAttachEmbeddedWindow.Stub.asInterface(service);
+ }
+ if (isReadyToAttach()) {
+ attachEmbedded();
+ }
+ }
+
+ public void onServiceDisconnected(ComponentName className) {
+ Log.d(TAG, "Service Disconnected");
+ mIAttachEmbeddedWindow = null;
+ synchronized (mLock) {
+ mIsAttached = false;
+ }
+ }
+ };
+
+ private boolean isReadyToAttach() {
+ synchronized (mLock) {
+ if (!mSurfaceCreated) {
+ Log.d(TAG, "surface is not created");
+ }
+ if (mIAttachEmbeddedWindow == null) {
+ Log.d(TAG, "Service is not attached");
+ }
+ if (mIsAttached) {
+ Log.d(TAG, "Already attached");
+ }
+
+ return mSurfaceCreated && mIAttachEmbeddedWindow != null && !mIsAttached;
+ }
+ }
+
+ private void attachEmbedded() {
+ synchronized (mLock) {
+ mIsAttached = true;
+ }
+ try {
+ mIAttachEmbeddedWindow.attachEmbedded(mSurfaceView.getHostToken(), mSizes[0].x,
+ mSizes[0].y, mDisplayId, mDelayMs, new IAttachEmbeddedWindowCallback.Stub() {
+ @Override
+ public void onEmbeddedWindowAttached(SurfacePackage surfacePackage) {
+ mHandler.post(() -> {
+ mSurfacePackage = surfacePackage;
+ mSurfaceView.setChildSurfacePackage(surfacePackage);
+ mReadyLatch.countDown();
+ });
+ }
+ });
+ } catch (RemoteException e) {
+ }
+ }
+
+ @Override
+ public void start(Context context, FrameLayout parent) {
+ mDisplayId = context.getDisplayId();
+ mHandler = new Handler(Looper.getMainLooper());
+
+ Intent intent = new Intent(context, EmbeddedSCVHService.class);
+ intent.setAction(EmbeddedSCVHService.class.getName());
+ context.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+
+ mSurfaceView = new SurfaceView(context);
+ mSurfaceView.getHolder().addCallback(mCallback);
+
+ FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(mSizes[0].x,
+ mSizes[0].y);
+ layoutParams.gravity = Gravity.CENTER;
+ parent.addView(mSurfaceView, layoutParams);
+ }
+
+ @Override
+ public void waitForReady() {
+
+ try {
+ mReadyLatch.await(5, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ }
+
+ assertEquals("Timed out waiting for setup", 0, mReadyLatch.getCount());
+ assertNotNull("SurfacePackage is null", mSurfacePackage);
+
+ mHandler.post(mRunnable);
+ }
+
+ @Override
+ public void end() {
+ mHandler.removeCallbacks(mRunnable);
+ }
+}
diff --git a/tests/SurfaceControlViewHostTest/Android.bp b/tests/SurfaceControlViewHostTest/Android.bp
index 0127ba559500..99567b9a10b4 100644
--- a/tests/SurfaceControlViewHostTest/Android.bp
+++ b/tests/SurfaceControlViewHostTest/Android.bp
@@ -25,7 +25,10 @@ package {
android_test {
name: "SurfaceControlViewHostTest",
- srcs: ["**/*.java"],
+ srcs: [
+ "**/*.aidl",
+ "**/*.java",
+ ],
platform_apis: true,
certificate: "platform",
}
diff --git a/tests/SurfaceControlViewHostTest/AndroidManifest.xml b/tests/SurfaceControlViewHostTest/AndroidManifest.xml
index 7e9a04dfa82c..e50cbc52a5b8 100644
--- a/tests/SurfaceControlViewHostTest/AndroidManifest.xml
+++ b/tests/SurfaceControlViewHostTest/AndroidManifest.xml
@@ -24,6 +24,16 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
+ <activity android:name="SurfaceControlViewHostSyncTest"
+ android:label="View Embedding Test Sync"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ <service android:name=".EmbeddedWindowService"
+ android:process="com.android.test.viewembed.embedded_process"/>
</application>
diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/EmbeddedWindowService.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/EmbeddedWindowService.java
new file mode 100644
index 000000000000..abc15b49ad98
--- /dev/null
+++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/EmbeddedWindowService.java
@@ -0,0 +1,109 @@
+/*
+ * 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 com.android.test.viewembed;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+
+import android.annotation.Nullable;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.hardware.display.DisplayManager;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.view.Display;
+import android.view.Gravity;
+import android.view.SurfaceControlViewHost;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+public class EmbeddedWindowService extends Service {
+ private static final String TAG = "EmbeddedWindowService";
+ private SurfaceControlViewHost mVr;
+
+ private Handler mHandler;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mHandler = new Handler(Looper.getMainLooper());
+ }
+
+ @Nullable
+ @Override
+ public IBinder onBind(Intent intent) {
+ // Return the interface
+ return new AttachEmbeddedWindow();
+ }
+
+ public static class SlowView extends TextView {
+
+ public SlowView(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ try {
+ Thread.sleep(250);
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+
+ private class AttachEmbeddedWindow extends IAttachEmbeddedWindow.Stub {
+ @Override
+ public void attachEmbedded(IBinder hostToken, int width, int height,
+ IAttachEmbeddedWindowCallback callback) {
+ mHandler.post(() -> {
+ Context context = EmbeddedWindowService.this;
+ Display display = getApplicationContext().getSystemService(
+ DisplayManager.class).getDisplay(DEFAULT_DISPLAY);
+ mVr = new SurfaceControlViewHost(context, display, hostToken);
+ FrameLayout content = new FrameLayout(context);
+
+ SlowView slowView = new SlowView(context);
+ slowView.setText("INSIDE TEXT");
+ slowView.setGravity(Gravity.CENTER);
+ slowView.setTextColor(Color.BLACK);
+ slowView.setBackgroundColor(Color.CYAN);
+ content.addView(slowView);
+ WindowManager.LayoutParams lp =
+ new WindowManager.LayoutParams(width, height, TYPE_APPLICATION,
+ 0, PixelFormat.OPAQUE);
+ lp.setTitle("EmbeddedWindow");
+
+ mVr.setView(content, lp);
+ try {
+ callback.onEmbeddedWindowAttached(mVr.getSurfacePackage());
+ } catch (RemoteException e) {
+ }
+ });
+ }
+ @Override
+ public void relayout(WindowManager.LayoutParams lp) {
+ mHandler.post(() -> mVr.relayout(lp));
+ }
+ }
+}
diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/IAttachEmbeddedWindow.aidl b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/IAttachEmbeddedWindow.aidl
new file mode 100644
index 000000000000..9e9faf03ba1c
--- /dev/null
+++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/IAttachEmbeddedWindow.aidl
@@ -0,0 +1,26 @@
+/*
+ * 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 com.android.test.viewembed;
+
+import android.os.IBinder;
+import com.android.test.viewembed.IAttachEmbeddedWindowCallback;
+import android.view.WindowManager.LayoutParams;
+
+interface IAttachEmbeddedWindow {
+ void attachEmbedded(IBinder hostToken, int width, int height, in IAttachEmbeddedWindowCallback callback);
+ void relayout(in LayoutParams lp);
+} \ No newline at end of file
diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/IAttachEmbeddedWindowCallback.aidl b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/IAttachEmbeddedWindowCallback.aidl
new file mode 100644
index 000000000000..c45c24d4b872
--- /dev/null
+++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/IAttachEmbeddedWindowCallback.aidl
@@ -0,0 +1,23 @@
+/*
+ * 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 com.android.test.viewembed;
+
+import android.view.SurfaceControlViewHost.SurfacePackage;
+
+interface IAttachEmbeddedWindowCallback {
+ void onEmbeddedWindowAttached(in SurfacePackage surfacePackage);
+} \ No newline at end of file
diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostSyncTest.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostSyncTest.java
new file mode 100644
index 000000000000..ea727b91768d
--- /dev/null
+++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostSyncTest.java
@@ -0,0 +1,215 @@
+/*
+ * 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 com.android.test.viewembed;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.SurfaceControlViewHost.SurfacePackage;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.Switch;
+import android.window.SurfaceSyncGroup;
+
+public class SurfaceControlViewHostSyncTest extends Activity implements SurfaceHolder.Callback {
+ private static final String TAG = "SurfaceControlViewHostSyncTest";
+ private SurfaceView mSv;
+
+ private final Object mLock = new Object();
+ private boolean mIsAttached;
+ private boolean mSurfaceCreated;
+
+ private IAttachEmbeddedWindow mIAttachEmbeddedWindow;
+ private SurfacePackage mSurfacePackage;
+
+ private final Point[] mSizes = new Point[]{new Point(500, 500), new Point(700, 400),
+ new Point(300, 800), new Point(200, 200)};
+ private int mLastSizeIndex = 0;
+
+ private boolean mSync = true;
+
+ private final ServiceConnection mConnection = new ServiceConnection() {
+ // Called when the connection with the service is established
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ Log.d(TAG, "Service Connected");
+ synchronized (mLock) {
+ mIAttachEmbeddedWindow = IAttachEmbeddedWindow.Stub.asInterface(service);
+ }
+ loadEmbedded();
+ }
+
+ public void onServiceDisconnected(ComponentName className) {
+ Log.d(TAG, "Service Disconnected");
+ mIAttachEmbeddedWindow = null;
+ }
+ };
+
+ protected void onCreate(Bundle savedInstanceState) {
+ FrameLayout content = new FrameLayout(this);
+ super.onCreate(savedInstanceState);
+ mSv = new SurfaceView(this);
+ Button button = new Button(this);
+ Switch enableSyncButton = new Switch(this);
+ content.addView(mSv, new FrameLayout.LayoutParams(
+ mSizes[0].x, mSizes[0].y, Gravity.CENTER_HORIZONTAL | Gravity.TOP));
+ content.addView(button, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.LEFT | Gravity.BOTTOM));
+ content.addView(enableSyncButton,
+ new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.RIGHT | Gravity.BOTTOM));
+ setContentView(content);
+
+ mSv.setZOrderOnTop(false);
+ mSv.getHolder().addCallback(this);
+
+ button.setText("Change Size");
+ enableSyncButton.setText("Enable Sync");
+ enableSyncButton.setChecked(true);
+ button.setOnClickListener(v -> {
+ resize();
+ });
+
+ enableSyncButton.setOnCheckedChangeListener((buttonView, isChecked) -> {
+ mSync = isChecked;
+ });
+
+ Intent intent = new Intent(this, EmbeddedWindowService.class);
+ intent.setAction(IAttachEmbeddedWindow.class.getName());
+ Log.d(TAG, "bindService");
+ bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+ }
+
+ private void resize() {
+ if (mSurfacePackage == null) {
+ return;
+ }
+ Point size = mSizes[mLastSizeIndex % mSizes.length];
+
+ Runnable svResizeRunnable = () -> {
+ mSv.getLayoutParams().width = size.x;
+ mSv.getLayoutParams().height = size.y;
+ mSv.requestLayout();
+ };
+
+ Runnable resizeRunnable = () -> {
+ try {
+ final WindowManager.LayoutParams lp =
+ new WindowManager.LayoutParams(size.x, size.y,
+ WindowManager.LayoutParams.TYPE_APPLICATION, 0,
+ PixelFormat.TRANSPARENT);
+ mIAttachEmbeddedWindow.relayout(lp);
+ } catch (RemoteException e) {
+ }
+ };
+
+ if (mSync) {
+ SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(TAG);
+ syncGroup.addToSync(getWindow().getRootSurfaceControl(), svResizeRunnable);
+ syncGroup.addToSync(mSurfacePackage, resizeRunnable);
+ syncGroup.markSyncReady();
+ } else {
+ svResizeRunnable.run();
+ resizeRunnable.run();
+ }
+
+ mLastSizeIndex++;
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ synchronized (mLock) {
+ mSurfaceCreated = true;
+ }
+ attachEmbedded();
+ }
+
+ private boolean isReadyToAttach() {
+ synchronized (mLock) {
+ if (!mSurfaceCreated) {
+ Log.d(TAG, "surface is not created");
+ }
+ if (mIAttachEmbeddedWindow == null) {
+ Log.d(TAG, "Service is not attached");
+ }
+ if (mIsAttached) {
+ Log.d(TAG, "Already attached");
+ }
+
+ return mSurfaceCreated && mIAttachEmbeddedWindow != null && !mIsAttached
+ && mSurfacePackage != null;
+ }
+ }
+
+ private void loadEmbedded() {
+ try {
+ mIAttachEmbeddedWindow.attachEmbedded(mSv.getHostToken(), mSizes[0].x, mSizes[0].y,
+ new IAttachEmbeddedWindowCallback.Stub() {
+ @Override
+ public void onEmbeddedWindowAttached(SurfacePackage surfacePackage) {
+ getMainThreadHandler().post(() -> {
+ mSurfacePackage = surfacePackage;
+ attachEmbedded();
+ });
+ }
+ });
+ mLastSizeIndex++;
+ } catch (RemoteException e) {
+ }
+ }
+
+ private void attachEmbedded() {
+ if (!isReadyToAttach()) {
+ return;
+ }
+
+ synchronized (mLock) {
+ mIsAttached = true;
+ }
+ mSv.setChildSurfacePackage(mSurfacePackage);
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ synchronized (mLock) {
+ mSurfaceCreated = false;
+ }
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ Log.d(TAG, "onStart");
+ resize();
+ }
+}