/* * Copyright (C) 2023 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.server.wm; import static android.server.wm.BuildUtils.HW_TIMEOUT_MULTIPLIER; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import android.app.Instrumentation; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.Rect; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import android.server.wm.scvh.IAttachEmbeddedWindow; import android.server.wm.scvh.SurfaceSyncGroupActivity; import android.util.Log; import android.view.SurfaceControl; import android.view.SurfaceControlViewHost.SurfacePackage; import android.view.View; import android.view.ViewTreeObserver; import android.view.cts.surfacevalidator.BitmapPixelChecker; import android.window.SurfaceSyncGroup; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.rule.ActivityTestRule; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @Presubmit public class SurfaceSyncGroupTests { private static final String TAG = "SurfaceSyncGroupTests"; @Rule public ActivityTestRule mActivityRule = new ActivityTestRule<>( SurfaceSyncGroupActivity.class); private SurfaceSyncGroupActivity mActivity; Instrumentation mInstrumentation; @Before public void setup() { mActivity = mActivityRule.getActivity(); mInstrumentation = InstrumentationRegistry.getInstrumentation(); } @Test public void testProcessCrash() { var data = mActivity.setupEmbeddedSCVH(); SurfacePackage surfacePackage = data.first; IAttachEmbeddedWindow iAttachEmbeddedWindow = data.second; CountDownLatch finishedRunLatch = new CountDownLatch(1); mActivity.runOnUiThread(() -> { SurfaceSyncGroup surfaceSyncGroup = new SurfaceSyncGroup(TAG); surfaceSyncGroup.add(surfacePackage, () -> { try { iAttachEmbeddedWindow.sendCrash(); } catch (RemoteException e) { Log.e(TAG, "Failed to send crash to embedded"); } }); surfaceSyncGroup.add(mActivity.getWindow().getRootSurfaceControl(), null /* runnable */); // Add a transaction committed listener to make sure the transaction has been applied // even though one of the processes involved crashed. SurfaceControl.Transaction t = new SurfaceControl.Transaction(); t.addTransactionCommittedListener(Runnable::run, finishedRunLatch::countDown); surfaceSyncGroup.addTransaction(t); surfaceSyncGroup.markSyncReady(); }); try { finishedRunLatch.await(5, TimeUnit.SECONDS); } catch (InterruptedException e) { Log.e(TAG, "Failed to wait for transaction committed callback"); } assertEquals("Failed to apply transaction for SurfaceSyncGroup", 0, finishedRunLatch.getCount()); } /** * This test will ensure that if multiple SurfaceSyncGroups are crated for the same ViewRootImpl * the SurfaceSyncGroups will maintain an order. The scenario that could occur is the following: * 1. SSG1 is created that includes the target VRI. There could be other VRIs in SSG1 * 2. target VRI draws its frame and is ready, but SSG1 is still waiting on other things in the * SSG * 3. Another SSG2 is created for the target VRI. The second frame renders and is ready and this * SSG has nothing else to wait on. SSG2 will apply at this point, even though SSG1 has not * 4. Frame2 will get to SF first and possibly later Frame1 will get to SF when SSG1 completes. *

* This test forces that behavior by ensuring the first SSG does not complete until the second * SSG gets its buffer and is considered complete. It waits a bit to ensure it had time to * make it to SF first. Then later it marks the first SSG as ready so that buffer could get * sent to SF. *

* With the fix in VRI, the second SSG will not complete until the first SSG has been submitted * to SF, ensuring that the frames are submitted in order. */ @Test public void testOverlappingSyncsEnsureOrder() throws InterruptedException { CountDownLatch secondDrawCompleteLatch = new CountDownLatch(1); CountDownLatch bothSyncGroupsComplete = new CountDownLatch(2); final SurfaceSyncGroup firstSsg = new SurfaceSyncGroup(TAG + "-first"); final SurfaceSyncGroup secondSsg = new SurfaceSyncGroup(TAG + "-second"); View backgroundView = mActivity.getBackgroundView(); mActivity.runOnUiThread(() -> { firstSsg.add(backgroundView.getRootSurfaceControl(), () -> backgroundView.setBackgroundColor(Color.RED)); addSecondSyncGroup(secondSsg, secondDrawCompleteLatch, bothSyncGroupsComplete); }); assertTrue("Failed to draw two frames", secondDrawCompleteLatch.await(5, TimeUnit.SECONDS)); // Add a bit of a delay to make sure the second frame could have been sent to SF Thread.sleep(HW_TIMEOUT_MULTIPLIER * 32L); SurfaceControl.Transaction t = new SurfaceControl.Transaction(); t.addTransactionCommittedListener(Runnable::run, bothSyncGroupsComplete::countDown); firstSsg.addTransaction(t); firstSsg.markSyncReady(); assertTrue("Failed to wait for both SurfaceSyncGroups to apply", bothSyncGroupsComplete.await(HW_TIMEOUT_MULTIPLIER * 5L, TimeUnit.SECONDS)); validateScreenshot(); } private void addSecondSyncGroup(SurfaceSyncGroup surfaceSyncGroup, CountDownLatch waitForSecondDraw, CountDownLatch bothSyncGroupsComplete) { View backgroundView = mActivity.getBackgroundView(); ViewTreeObserver viewTreeObserver = backgroundView.getViewTreeObserver(); viewTreeObserver.registerFrameCommitCallback(() -> mActivity.runOnUiThread(() -> { surfaceSyncGroup.add(backgroundView.getRootSurfaceControl(), () -> backgroundView.setBackgroundColor(Color.BLUE)); SurfaceControl.Transaction t = new SurfaceControl.Transaction(); t.addTransactionCommittedListener(Runnable::run, bothSyncGroupsComplete::countDown); surfaceSyncGroup.addTransaction(t); surfaceSyncGroup.markSyncReady(); viewTreeObserver.registerFrameCommitCallback(waitForSecondDraw::countDown); })); } private void validateScreenshot() { Bitmap screenshot = mInstrumentation.getUiAutomation().takeScreenshot( mActivity.getWindow()); assertNotNull("Failed to generate a screenshot", screenshot); Bitmap swBitmap = screenshot.copy(Bitmap.Config.ARGB_8888, false); screenshot.recycle(); BitmapPixelChecker pixelChecker = new BitmapPixelChecker(Color.BLUE); int halfWidth = swBitmap.getWidth() / 2; int halfHeight = swBitmap.getHeight() / 2; // We don't need to check all the pixels since we only care that at least some of them are // blue. If the buffers were submitted out of order, all the pixels will be red. Rect bounds = new Rect(halfWidth, halfHeight, halfWidth + 10, halfHeight + 10); int numMatchingPixels = pixelChecker.getNumMatchingPixels(swBitmap, bounds); assertEquals("Expected 100 received " + numMatchingPixels + " matching pixels", 100, numMatchingPixels); swBitmap.recycle(); } }