Loading core/java/android/view/SurfaceView.java +7 −0 Original line number Diff line number Diff line Loading @@ -1789,4 +1789,11 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall t.apply(); } } /** * @hide */ public void syncNextFrame(Transaction t) { mBlastBufferQueue.setSyncTransaction(t); } } core/java/android/view/ViewRootImpl.java +68 −0 Original line number Diff line number Diff line Loading @@ -195,6 +195,7 @@ import android.view.contentcapture.MainContentCaptureSession; import android.view.inputmethod.InputMethodManager; import android.widget.Scroller; import android.window.ClientWindowFrames; import android.window.SurfaceSyncer; import android.window.WindowOnBackInvokedDispatcher; import com.android.internal.R; Loading Loading @@ -10873,4 +10874,71 @@ public final class ViewRootImpl implements ViewParent, IWindowSession getWindowSession() { return mWindowSession; } private void registerCallbacksForSync( final SurfaceSyncer.SyncBufferCallback syncBufferCallback) { if (!isHardwareEnabled()) { // TODO: correctly handle when hardware disabled syncBufferCallback.onBufferReady(null); return; } mAttachInfo.mThreadedRenderer.registerRtFrameCallback(new FrameDrawingCallback() { @Override public void onFrameDraw(long frame) { } @Override public HardwareRenderer.FrameCommitCallback onFrameDraw(int syncResult, long frame) { if (DEBUG_BLAST) { Log.d(mTag, "Received frameDrawingCallback syncResult=" + syncResult + " frameNum=" + frame + "."); } final Transaction t = new Transaction(); // If the syncResults are SYNC_LOST_SURFACE_REWARD_IF_FOUND or // SYNC_CONTEXT_IS_STOPPED it means nothing will draw. There's no need to set up // any blast sync or commit callback, and the code should directly call // pendingDrawFinished. if ((syncResult & (SYNC_LOST_SURFACE_REWARD_IF_FOUND | SYNC_CONTEXT_IS_STOPPED)) != 0) { t.merge(mBlastBufferQueue.gatherPendingTransactions(frame)); syncBufferCallback.onBufferReady(t); return null; } mBlastBufferQueue.setSyncTransaction(t); if (DEBUG_BLAST) { Log.d(mTag, "Setting up sync and frameCommitCallback"); } return didProduceBuffer -> { if (DEBUG_BLAST) { Log.d(mTag, "Received frameCommittedCallback" + " lastAttemptedDrawFrameNum=" + frame + " didProduceBuffer=" + didProduceBuffer); } // If frame wasn't drawn, clear out the next transaction so it doesn't affect // the next draw attempt. The next transaction and transaction complete callback // were only set for the current draw attempt. if (!didProduceBuffer) { mBlastBufferQueue.setSyncTransaction(null); // Gather the transactions that were sent to mergeWithNextTransaction // 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. t.merge(mBlastBufferQueue.gatherPendingTransactions(frame)); } syncBufferCallback.onBufferReady(t); }; } }); } public final SurfaceSyncer.SyncTarget mSyncTarget = syncBufferCallback -> registerCallbacksForSync(syncBufferCallback); } core/java/android/window/SurfaceSyncer.java 0 → 100644 +377 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.window; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UiThread; import android.os.Handler; import android.os.Looper; import android.util.Log; import android.util.SparseArray; import android.view.SurfaceControl.Transaction; import android.view.SurfaceView; import android.view.View; import com.android.internal.annotations.GuardedBy; import java.util.HashSet; import java.util.Set; import java.util.function.Consumer; import java.util.function.Supplier; /** * Used to organize syncs for surfaces. * * The SurfaceSyncer allows callers to add desired syncs into a set and wait for them to all * complete before getting a callback. The purpose of the Syncer is to be an accounting mechanism * so each sync implementation doesn't need to handle it themselves. The Syncer class is used the * following way. * * 1. {@link #setupSync(Runnable)} is called * 2. {@link #addToSync(int, SyncTarget)} is called for every SyncTarget object that wants to be * included in the sync. If the addSync is called for a View or 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. * 3. {@link #markSyncReady(int)} should be called when all the {@link SyncTarget}s have been added * to the SyncSet. Now the SyncSet is closed and no more SyncTargets can be added to it. * 4. The SyncSet will gather the data for each SyncTarget using the steps described below. When * all the SyncTargets have finished, the syncRequestComplete will be invoked and the transaction * will either be applied or sent to the caller. In most cases, only the SurfaceSyncer 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 SyncSet} * 1. Each SyncableTarget will get a {@link SyncTarget#onReadyToSync} callback that contains * a {@link SyncBufferCallback}. * 2. Each {@link SyncTarget} needs to invoke {@link SyncBufferCallback#onBufferReady(Transaction)}. * This makes sure the SyncSet knows when the SyncTarget is complete, allowing the SyncSet to get * the Transaction that contains the buffer. * 3. When the final SyncBufferCallback finishes for the SyncSet, the syncRequestComplete Consumer * will be invoked with the transaction that contains all information requested in the sync. This * could include buffers and geometry changes. The buffer update will include the UI changes that * were requested for the View. * * @hide */ public class SurfaceSyncer { private static final String TAG = "SurfaceSyncer"; private static final boolean DEBUG = false; private static Supplier<Transaction> sTransactionFactory = Transaction::new; private final Object mSyncSetLock = new Object(); @GuardedBy("mSyncSetLock") private final SparseArray<SyncSet> mSyncSets = new SparseArray<>(); @GuardedBy("mSyncSetLock") private int mIdCounter = 0; /** * @hide */ public static void setTransactionFactory(Supplier<Transaction> transactionFactory) { sTransactionFactory = transactionFactory; } /** * Starts a sync and will automatically apply the final, merged transaction. * * @param onComplete The runnable that is invoked when the sync has completed. This will run on * the same thread that the sync was started on. * @return The syncId for the newly created sync. * @see #setupSync(Consumer) */ public int setupSync(@Nullable Runnable onComplete) { Handler handler = new Handler(Looper.myLooper()); return setupSync(transaction -> { transaction.apply(); handler.post(onComplete); }); } /** * Starts a sync. * * @param syncRequestComplete The complete callback that contains the syncId and transaction * with all the sync data merged. * @return The syncId for the newly created sync. * @hide * @see #setupSync(Runnable) */ public int setupSync(@NonNull Consumer<Transaction> syncRequestComplete) { synchronized (mSyncSetLock) { mIdCounter++; if (DEBUG) { Log.d(TAG, "setupSync " + mIdCounter); } SyncSet syncSet = new SyncSet(mIdCounter, syncRequestComplete); mSyncSets.put(mIdCounter, syncSet); return mIdCounter; } } /** * Mark the sync set as ready to complete. No more data can be added to the specified syncId. * Once the sync set is marked as ready, it will be able to complete once all Syncables in the * set have completed their sync * * @param syncId The syncId to mark as ready. */ public void markSyncReady(int syncId) { SyncSet syncSet; synchronized (mSyncSetLock) { syncSet = mSyncSets.get(syncId); mSyncSets.remove(syncId); } if (syncSet == null) { Log.e(TAG, "Failed to find syncSet for syncId=" + syncId); return; } syncSet.markSyncReady(); } /** * Add a SurfaceView to a sync set. This is different than {@link #addToSync(int, View)} because * it requires the caller to notify the start and finish drawing in order to sync. * * @param syncId The syncId to add an entry to. * @param surfaceView The SurfaceView to add to the sync. * @param frameCallbackConsumer The callback that's invoked to allow the caller to notify the * Syncer when the SurfaceView has started drawing and finished. * * @return true if the SurfaceView was successfully added to the SyncSet, false otherwise. */ @UiThread public boolean addToSync(int syncId, SurfaceView surfaceView, Consumer<SurfaceViewFrameCallback> frameCallbackConsumer) { return addToSync(syncId, new SurfaceViewSyncTarget(surfaceView, frameCallbackConsumer)); } /** * Add a View's rootView to a sync set. * * @param syncId The syncId to add an entry to. * @param view The view where the root will be add to the sync set * * @return true if the View was successfully added to the SyncSet, false otherwise. */ @UiThread public boolean addToSync(int syncId, @NonNull View view) { return addToSync(syncId, view.getViewRootImpl().mSyncTarget); } /** * Add a {@link SyncTarget} to a sync set. The sync set will wait for all * SyncableSurfaces to complete before notifying. * * @param syncId The syncId to add an entry to. * @param syncTarget A SyncableSurface that implements how to handle syncing * buffers. * * @return true if the SyncTarget was successfully added to the SyncSet, false otherwise. */ public boolean addToSync(int syncId, @NonNull SyncTarget syncTarget) { SyncSet syncSet = getAndValidateSyncSet(syncId); if (syncSet == null) { return false; } if (DEBUG) { Log.d(TAG, "addToSync id=" + syncId); } syncSet.addSyncableSurface(syncTarget); return true; } /** * Add a transaction to a specific sync so it can be merged along with the frames from the * Syncables in the set. This is so the caller can add arbitrary transaction info that will be * applied at the same time as the buffers * @param syncId The syncId where the transaction will be merged to. * @param t The transaction to merge in the sync set. */ public void addTransactionToSync(int syncId, Transaction t) { SyncSet syncSet = getAndValidateSyncSet(syncId); if (syncSet != null) { syncSet.addTransactionToSync(t); } } private SyncSet getAndValidateSyncSet(int syncId) { SyncSet syncSet; synchronized (mSyncSetLock) { syncSet = mSyncSets.get(syncId); } if (syncSet == null) { Log.e(TAG, "Failed to find sync for id=" + syncId); return null; } return syncSet; } /** * A SyncTarget that can be added to a sync set. */ public interface SyncTarget { /** * Called when the Syncable is ready to begin handing a sync request. When invoked, the * implementor is required to call {@link SyncBufferCallback#onBufferReady(Transaction)} * and {@link SyncBufferCallback#onBufferReady(Transaction)} in order for this Syncable * to be marked as complete. * * @param syncBufferCallback A SyncBufferCallback that the caller must invoke onBufferReady */ void onReadyToSync(SyncBufferCallback syncBufferCallback); } /** * 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. */ public interface SyncBufferCallback { /** * Invoked when the transaction contains the buffer and is ready to sync. * * @param t The transaction that contains the buffer to be synced. This can be null if * there's nothing to sync */ void onBufferReady(@Nullable Transaction t); } /** * Class that collects the {@link SyncTarget}s and notifies when all the surfaces have * a frame ready. */ private static class SyncSet { private final Object mLock = new Object(); @GuardedBy("mLock") private final Set<Integer> mPendingSyncs = new HashSet<>(); @GuardedBy("mLock") private final Transaction mTransaction = sTransactionFactory.get(); @GuardedBy("mLock") private boolean mSyncReady; private final int mSyncId; private final Consumer<Transaction> mSyncRequestCompleteCallback; private SyncSet(int syncId, Consumer<Transaction> syncRequestComplete) { mSyncId = syncId; mSyncRequestCompleteCallback = syncRequestComplete; } void addSyncableSurface(SyncTarget syncTarget) { SyncBufferCallback syncBufferCallback = new SyncBufferCallback() { @Override public void onBufferReady(Transaction t) { synchronized (mLock) { if (t != null) { mTransaction.merge(t); } mPendingSyncs.remove(hashCode()); checkIfSyncIsComplete(); } } }; synchronized (mLock) { mPendingSyncs.add(syncBufferCallback.hashCode()); } syncTarget.onReadyToSync(syncBufferCallback); } void markSyncReady() { synchronized (mLock) { mSyncReady = true; checkIfSyncIsComplete(); } } @GuardedBy("mLock") private void checkIfSyncIsComplete() { if (!mSyncReady || !mPendingSyncs.isEmpty()) { if (DEBUG) { Log.d(TAG, "Syncable is not complete. mSyncReady=" + mSyncReady + " mPendingSyncs=" + mPendingSyncs.size()); } return; } if (DEBUG) { Log.d(TAG, "Successfully finished sync id=" + mSyncId); } mSyncRequestCompleteCallback.accept(mTransaction); } /** * Add a Transaction to this sync set. This allows the caller to provide other info that * should be synced with the buffers. */ void addTransactionToSync(Transaction t) { synchronized (mLock) { mTransaction.merge(t); } } } /** * Wrapper class to help synchronize SurfaceViews */ private static class SurfaceViewSyncTarget implements SyncTarget { private final SurfaceView mSurfaceView; private final Consumer<SurfaceViewFrameCallback> mFrameCallbackConsumer; SurfaceViewSyncTarget(SurfaceView surfaceView, Consumer<SurfaceViewFrameCallback> frameCallbackConsumer) { mSurfaceView = surfaceView; mFrameCallbackConsumer = frameCallbackConsumer; } @Override public void onReadyToSync(SyncBufferCallback syncBufferCallback) { Transaction mTransaction = sTransactionFactory.get(); mFrameCallbackConsumer.accept(new SurfaceViewFrameCallback() { @Override public void onFrameStarted() { mSurfaceView.syncNextFrame(mTransaction); } @Override public void onFrameComplete() { syncBufferCallback.onBufferReady(mTransaction); } }); } } /** * A frame callback that is used to synchronize SurfaceViews. The owner of the SurfaceView must * implement onFrameStarted and onFrameComplete when trying to sync the SurfaceView. This is to * ensure the sync knows when the frame is ready to add to the sync. */ public interface SurfaceViewFrameCallback { /** * Called when the SurfaceView is going to render a frame */ void onFrameStarted(); /** * Called when the SurfaceView has finished rendering a frame. */ void onFrameComplete(); } } services/tests/wmtests/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -57,6 +57,7 @@ android_test { "ub-uiautomator", "hamcrest-library", "platform-compat-test-rules", "CtsSurfaceValidatorLib", ], libs: [ Loading services/tests/wmtests/AndroidManifest.xml +8 −0 Original line number Diff line number Diff line Loading @@ -43,6 +43,8 @@ <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE" /> <uses-permission android:name="android.permission.CAPTURE_BLACKOUT_CONTENT"/> <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <!-- TODO: Remove largeHeap hack when memory leak is fixed (b/123984854) --> <application android:debuggable="true" Loading Loading @@ -78,6 +80,12 @@ android:turnScreenOn="true" android:showWhenLocked="true" /> <activity android:name="com.android.server.wm.ScreenshotTests$ScreenshotActivity" /> <activity android:name="android.view.cts.surfacevalidator.CapturedActivity"/> <service android:name="android.view.cts.surfacevalidator.LocalMediaProjectionService" android:foregroundServiceType="mediaProjection" android:enabled="true"> </service> </application> <instrumentation Loading Loading
core/java/android/view/SurfaceView.java +7 −0 Original line number Diff line number Diff line Loading @@ -1789,4 +1789,11 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall t.apply(); } } /** * @hide */ public void syncNextFrame(Transaction t) { mBlastBufferQueue.setSyncTransaction(t); } }
core/java/android/view/ViewRootImpl.java +68 −0 Original line number Diff line number Diff line Loading @@ -195,6 +195,7 @@ import android.view.contentcapture.MainContentCaptureSession; import android.view.inputmethod.InputMethodManager; import android.widget.Scroller; import android.window.ClientWindowFrames; import android.window.SurfaceSyncer; import android.window.WindowOnBackInvokedDispatcher; import com.android.internal.R; Loading Loading @@ -10873,4 +10874,71 @@ public final class ViewRootImpl implements ViewParent, IWindowSession getWindowSession() { return mWindowSession; } private void registerCallbacksForSync( final SurfaceSyncer.SyncBufferCallback syncBufferCallback) { if (!isHardwareEnabled()) { // TODO: correctly handle when hardware disabled syncBufferCallback.onBufferReady(null); return; } mAttachInfo.mThreadedRenderer.registerRtFrameCallback(new FrameDrawingCallback() { @Override public void onFrameDraw(long frame) { } @Override public HardwareRenderer.FrameCommitCallback onFrameDraw(int syncResult, long frame) { if (DEBUG_BLAST) { Log.d(mTag, "Received frameDrawingCallback syncResult=" + syncResult + " frameNum=" + frame + "."); } final Transaction t = new Transaction(); // If the syncResults are SYNC_LOST_SURFACE_REWARD_IF_FOUND or // SYNC_CONTEXT_IS_STOPPED it means nothing will draw. There's no need to set up // any blast sync or commit callback, and the code should directly call // pendingDrawFinished. if ((syncResult & (SYNC_LOST_SURFACE_REWARD_IF_FOUND | SYNC_CONTEXT_IS_STOPPED)) != 0) { t.merge(mBlastBufferQueue.gatherPendingTransactions(frame)); syncBufferCallback.onBufferReady(t); return null; } mBlastBufferQueue.setSyncTransaction(t); if (DEBUG_BLAST) { Log.d(mTag, "Setting up sync and frameCommitCallback"); } return didProduceBuffer -> { if (DEBUG_BLAST) { Log.d(mTag, "Received frameCommittedCallback" + " lastAttemptedDrawFrameNum=" + frame + " didProduceBuffer=" + didProduceBuffer); } // If frame wasn't drawn, clear out the next transaction so it doesn't affect // the next draw attempt. The next transaction and transaction complete callback // were only set for the current draw attempt. if (!didProduceBuffer) { mBlastBufferQueue.setSyncTransaction(null); // Gather the transactions that were sent to mergeWithNextTransaction // 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. t.merge(mBlastBufferQueue.gatherPendingTransactions(frame)); } syncBufferCallback.onBufferReady(t); }; } }); } public final SurfaceSyncer.SyncTarget mSyncTarget = syncBufferCallback -> registerCallbacksForSync(syncBufferCallback); }
core/java/android/window/SurfaceSyncer.java 0 → 100644 +377 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.window; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UiThread; import android.os.Handler; import android.os.Looper; import android.util.Log; import android.util.SparseArray; import android.view.SurfaceControl.Transaction; import android.view.SurfaceView; import android.view.View; import com.android.internal.annotations.GuardedBy; import java.util.HashSet; import java.util.Set; import java.util.function.Consumer; import java.util.function.Supplier; /** * Used to organize syncs for surfaces. * * The SurfaceSyncer allows callers to add desired syncs into a set and wait for them to all * complete before getting a callback. The purpose of the Syncer is to be an accounting mechanism * so each sync implementation doesn't need to handle it themselves. The Syncer class is used the * following way. * * 1. {@link #setupSync(Runnable)} is called * 2. {@link #addToSync(int, SyncTarget)} is called for every SyncTarget object that wants to be * included in the sync. If the addSync is called for a View or 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. * 3. {@link #markSyncReady(int)} should be called when all the {@link SyncTarget}s have been added * to the SyncSet. Now the SyncSet is closed and no more SyncTargets can be added to it. * 4. The SyncSet will gather the data for each SyncTarget using the steps described below. When * all the SyncTargets have finished, the syncRequestComplete will be invoked and the transaction * will either be applied or sent to the caller. In most cases, only the SurfaceSyncer 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 SyncSet} * 1. Each SyncableTarget will get a {@link SyncTarget#onReadyToSync} callback that contains * a {@link SyncBufferCallback}. * 2. Each {@link SyncTarget} needs to invoke {@link SyncBufferCallback#onBufferReady(Transaction)}. * This makes sure the SyncSet knows when the SyncTarget is complete, allowing the SyncSet to get * the Transaction that contains the buffer. * 3. When the final SyncBufferCallback finishes for the SyncSet, the syncRequestComplete Consumer * will be invoked with the transaction that contains all information requested in the sync. This * could include buffers and geometry changes. The buffer update will include the UI changes that * were requested for the View. * * @hide */ public class SurfaceSyncer { private static final String TAG = "SurfaceSyncer"; private static final boolean DEBUG = false; private static Supplier<Transaction> sTransactionFactory = Transaction::new; private final Object mSyncSetLock = new Object(); @GuardedBy("mSyncSetLock") private final SparseArray<SyncSet> mSyncSets = new SparseArray<>(); @GuardedBy("mSyncSetLock") private int mIdCounter = 0; /** * @hide */ public static void setTransactionFactory(Supplier<Transaction> transactionFactory) { sTransactionFactory = transactionFactory; } /** * Starts a sync and will automatically apply the final, merged transaction. * * @param onComplete The runnable that is invoked when the sync has completed. This will run on * the same thread that the sync was started on. * @return The syncId for the newly created sync. * @see #setupSync(Consumer) */ public int setupSync(@Nullable Runnable onComplete) { Handler handler = new Handler(Looper.myLooper()); return setupSync(transaction -> { transaction.apply(); handler.post(onComplete); }); } /** * Starts a sync. * * @param syncRequestComplete The complete callback that contains the syncId and transaction * with all the sync data merged. * @return The syncId for the newly created sync. * @hide * @see #setupSync(Runnable) */ public int setupSync(@NonNull Consumer<Transaction> syncRequestComplete) { synchronized (mSyncSetLock) { mIdCounter++; if (DEBUG) { Log.d(TAG, "setupSync " + mIdCounter); } SyncSet syncSet = new SyncSet(mIdCounter, syncRequestComplete); mSyncSets.put(mIdCounter, syncSet); return mIdCounter; } } /** * Mark the sync set as ready to complete. No more data can be added to the specified syncId. * Once the sync set is marked as ready, it will be able to complete once all Syncables in the * set have completed their sync * * @param syncId The syncId to mark as ready. */ public void markSyncReady(int syncId) { SyncSet syncSet; synchronized (mSyncSetLock) { syncSet = mSyncSets.get(syncId); mSyncSets.remove(syncId); } if (syncSet == null) { Log.e(TAG, "Failed to find syncSet for syncId=" + syncId); return; } syncSet.markSyncReady(); } /** * Add a SurfaceView to a sync set. This is different than {@link #addToSync(int, View)} because * it requires the caller to notify the start and finish drawing in order to sync. * * @param syncId The syncId to add an entry to. * @param surfaceView The SurfaceView to add to the sync. * @param frameCallbackConsumer The callback that's invoked to allow the caller to notify the * Syncer when the SurfaceView has started drawing and finished. * * @return true if the SurfaceView was successfully added to the SyncSet, false otherwise. */ @UiThread public boolean addToSync(int syncId, SurfaceView surfaceView, Consumer<SurfaceViewFrameCallback> frameCallbackConsumer) { return addToSync(syncId, new SurfaceViewSyncTarget(surfaceView, frameCallbackConsumer)); } /** * Add a View's rootView to a sync set. * * @param syncId The syncId to add an entry to. * @param view The view where the root will be add to the sync set * * @return true if the View was successfully added to the SyncSet, false otherwise. */ @UiThread public boolean addToSync(int syncId, @NonNull View view) { return addToSync(syncId, view.getViewRootImpl().mSyncTarget); } /** * Add a {@link SyncTarget} to a sync set. The sync set will wait for all * SyncableSurfaces to complete before notifying. * * @param syncId The syncId to add an entry to. * @param syncTarget A SyncableSurface that implements how to handle syncing * buffers. * * @return true if the SyncTarget was successfully added to the SyncSet, false otherwise. */ public boolean addToSync(int syncId, @NonNull SyncTarget syncTarget) { SyncSet syncSet = getAndValidateSyncSet(syncId); if (syncSet == null) { return false; } if (DEBUG) { Log.d(TAG, "addToSync id=" + syncId); } syncSet.addSyncableSurface(syncTarget); return true; } /** * Add a transaction to a specific sync so it can be merged along with the frames from the * Syncables in the set. This is so the caller can add arbitrary transaction info that will be * applied at the same time as the buffers * @param syncId The syncId where the transaction will be merged to. * @param t The transaction to merge in the sync set. */ public void addTransactionToSync(int syncId, Transaction t) { SyncSet syncSet = getAndValidateSyncSet(syncId); if (syncSet != null) { syncSet.addTransactionToSync(t); } } private SyncSet getAndValidateSyncSet(int syncId) { SyncSet syncSet; synchronized (mSyncSetLock) { syncSet = mSyncSets.get(syncId); } if (syncSet == null) { Log.e(TAG, "Failed to find sync for id=" + syncId); return null; } return syncSet; } /** * A SyncTarget that can be added to a sync set. */ public interface SyncTarget { /** * Called when the Syncable is ready to begin handing a sync request. When invoked, the * implementor is required to call {@link SyncBufferCallback#onBufferReady(Transaction)} * and {@link SyncBufferCallback#onBufferReady(Transaction)} in order for this Syncable * to be marked as complete. * * @param syncBufferCallback A SyncBufferCallback that the caller must invoke onBufferReady */ void onReadyToSync(SyncBufferCallback syncBufferCallback); } /** * 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. */ public interface SyncBufferCallback { /** * Invoked when the transaction contains the buffer and is ready to sync. * * @param t The transaction that contains the buffer to be synced. This can be null if * there's nothing to sync */ void onBufferReady(@Nullable Transaction t); } /** * Class that collects the {@link SyncTarget}s and notifies when all the surfaces have * a frame ready. */ private static class SyncSet { private final Object mLock = new Object(); @GuardedBy("mLock") private final Set<Integer> mPendingSyncs = new HashSet<>(); @GuardedBy("mLock") private final Transaction mTransaction = sTransactionFactory.get(); @GuardedBy("mLock") private boolean mSyncReady; private final int mSyncId; private final Consumer<Transaction> mSyncRequestCompleteCallback; private SyncSet(int syncId, Consumer<Transaction> syncRequestComplete) { mSyncId = syncId; mSyncRequestCompleteCallback = syncRequestComplete; } void addSyncableSurface(SyncTarget syncTarget) { SyncBufferCallback syncBufferCallback = new SyncBufferCallback() { @Override public void onBufferReady(Transaction t) { synchronized (mLock) { if (t != null) { mTransaction.merge(t); } mPendingSyncs.remove(hashCode()); checkIfSyncIsComplete(); } } }; synchronized (mLock) { mPendingSyncs.add(syncBufferCallback.hashCode()); } syncTarget.onReadyToSync(syncBufferCallback); } void markSyncReady() { synchronized (mLock) { mSyncReady = true; checkIfSyncIsComplete(); } } @GuardedBy("mLock") private void checkIfSyncIsComplete() { if (!mSyncReady || !mPendingSyncs.isEmpty()) { if (DEBUG) { Log.d(TAG, "Syncable is not complete. mSyncReady=" + mSyncReady + " mPendingSyncs=" + mPendingSyncs.size()); } return; } if (DEBUG) { Log.d(TAG, "Successfully finished sync id=" + mSyncId); } mSyncRequestCompleteCallback.accept(mTransaction); } /** * Add a Transaction to this sync set. This allows the caller to provide other info that * should be synced with the buffers. */ void addTransactionToSync(Transaction t) { synchronized (mLock) { mTransaction.merge(t); } } } /** * Wrapper class to help synchronize SurfaceViews */ private static class SurfaceViewSyncTarget implements SyncTarget { private final SurfaceView mSurfaceView; private final Consumer<SurfaceViewFrameCallback> mFrameCallbackConsumer; SurfaceViewSyncTarget(SurfaceView surfaceView, Consumer<SurfaceViewFrameCallback> frameCallbackConsumer) { mSurfaceView = surfaceView; mFrameCallbackConsumer = frameCallbackConsumer; } @Override public void onReadyToSync(SyncBufferCallback syncBufferCallback) { Transaction mTransaction = sTransactionFactory.get(); mFrameCallbackConsumer.accept(new SurfaceViewFrameCallback() { @Override public void onFrameStarted() { mSurfaceView.syncNextFrame(mTransaction); } @Override public void onFrameComplete() { syncBufferCallback.onBufferReady(mTransaction); } }); } } /** * A frame callback that is used to synchronize SurfaceViews. The owner of the SurfaceView must * implement onFrameStarted and onFrameComplete when trying to sync the SurfaceView. This is to * ensure the sync knows when the frame is ready to add to the sync. */ public interface SurfaceViewFrameCallback { /** * Called when the SurfaceView is going to render a frame */ void onFrameStarted(); /** * Called when the SurfaceView has finished rendering a frame. */ void onFrameComplete(); } }
services/tests/wmtests/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -57,6 +57,7 @@ android_test { "ub-uiautomator", "hamcrest-library", "platform-compat-test-rules", "CtsSurfaceValidatorLib", ], libs: [ Loading
services/tests/wmtests/AndroidManifest.xml +8 −0 Original line number Diff line number Diff line Loading @@ -43,6 +43,8 @@ <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE" /> <uses-permission android:name="android.permission.CAPTURE_BLACKOUT_CONTENT"/> <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <!-- TODO: Remove largeHeap hack when memory leak is fixed (b/123984854) --> <application android:debuggable="true" Loading Loading @@ -78,6 +80,12 @@ android:turnScreenOn="true" android:showWhenLocked="true" /> <activity android:name="com.android.server.wm.ScreenshotTests$ScreenshotActivity" /> <activity android:name="android.view.cts.surfacevalidator.CapturedActivity"/> <service android:name="android.view.cts.surfacevalidator.LocalMediaProjectionService" android:foregroundServiceType="mediaProjection" android:enabled="true"> </service> </application> <instrumentation Loading