1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.view.surfacecontrol.cts;
18 
19 import static android.server.wm.BuildUtils.HW_TIMEOUT_MULTIPLIER;
20 import static android.server.wm.app.Components.CRASHING_ACTIVITY;
21 
22 import static org.junit.Assert.assertEquals;
23 import static org.junit.Assert.assertNotNull;
24 import static org.junit.Assert.assertTrue;
25 
26 import android.app.ActivityManager;
27 import android.app.Instrumentation;
28 import android.graphics.Bitmap;
29 import android.graphics.Color;
30 import android.graphics.Rect;
31 import android.os.RemoteException;
32 import android.platform.test.annotations.Presubmit;
33 import android.provider.Settings;
34 import android.server.wm.settings.SettingsSession;
35 import android.util.Log;
36 import android.view.SurfaceControl;
37 import android.view.SurfaceControlViewHost.SurfacePackage;
38 import android.view.View;
39 import android.view.ViewTreeObserver;
40 import android.view.cts.surfacevalidator.BitmapPixelChecker;
41 import android.view.cts.util.aidl.IAttachEmbeddedWindow;
42 import android.window.SurfaceSyncGroup;
43 
44 import androidx.test.ext.junit.rules.ActivityScenarioRule;
45 import androidx.test.platform.app.InstrumentationRegistry;
46 
47 import com.android.compatibility.common.util.SystemUtil;
48 
49 import org.junit.After;
50 import org.junit.Before;
51 import org.junit.Rule;
52 import org.junit.Test;
53 
54 import java.util.concurrent.CountDownLatch;
55 import java.util.concurrent.TimeUnit;
56 
57 @Presubmit
58 public class SurfaceSyncGroupTests {
59     private static final String TAG = "SurfaceSyncGroupTests";
60 
61     @Rule
62     public ActivityScenarioRule<SurfaceSyncGroupActivity> mActivityRule =
63             new ActivityScenarioRule<>(SurfaceSyncGroupActivity.class);
64 
65     private SurfaceSyncGroupActivity mActivity;
66 
67     Instrumentation mInstrumentation;
68 
69     private SettingsSession<Integer> mHideDialogSetting;
70 
71     @Before
setup()72     public void setup() {
73         mActivityRule.getScenario().onActivity(activity -> mActivity = activity);
74         mInstrumentation = InstrumentationRegistry.getInstrumentation();
75         // One of the tests purposely crashes so disable showing the crash dialog to avoid breaking
76         // tests later on.
77         mHideDialogSetting = new SettingsSession<>(
78                 Settings.Global.getUriFor(Settings.Global.HIDE_ERROR_DIALOGS),
79                 Settings.Global::getInt, Settings.Global::putInt);
80         mHideDialogSetting.set(1);
81     }
82 
83     @After
tearDown()84     public void tearDown() {
85         if (mHideDialogSetting != null) mHideDialogSetting.close();
86         ActivityManager am = mActivity.getSystemService(ActivityManager.class);
87         SystemUtil.runWithShellPermissionIdentity(() -> am.forceStopPackage(
88                 CRASHING_ACTIVITY.getPackageName()));
89     }
90 
91     @Test
testProcessCrash()92     public void testProcessCrash() {
93         var data = mActivity.setupEmbeddedSCVH();
94         SurfacePackage surfacePackage = data.first;
95         IAttachEmbeddedWindow iAttachEmbeddedWindow = data.second;
96 
97         CountDownLatch finishedRunLatch = new CountDownLatch(1);
98 
99         mActivity.runOnUiThread(() -> {
100             SurfaceSyncGroup surfaceSyncGroup = new SurfaceSyncGroup(TAG);
101             surfaceSyncGroup.add(surfacePackage, () -> {
102                 try {
103                     iAttachEmbeddedWindow.sendCrash();
104                 } catch (RemoteException e) {
105                     Log.e(TAG, "Failed to send crash to embedded");
106                 }
107             });
108             surfaceSyncGroup.add(mActivity.getWindow().getRootSurfaceControl(),
109                     null /* runnable */);
110             // Add a transaction committed listener to make sure the transaction has been applied
111             // even though one of the processes involved crashed.
112             SurfaceControl.Transaction t = new SurfaceControl.Transaction();
113             t.addTransactionCommittedListener(Runnable::run, finishedRunLatch::countDown);
114             surfaceSyncGroup.addTransaction(t);
115             surfaceSyncGroup.markSyncReady();
116         });
117 
118         try {
119             finishedRunLatch.await(5, TimeUnit.SECONDS);
120         } catch (InterruptedException e) {
121             Log.e(TAG, "Failed to wait for transaction committed callback");
122         }
123 
124         assertEquals("Failed to apply transaction for SurfaceSyncGroup", 0,
125                 finishedRunLatch.getCount());
126     }
127 
128     /**
129      * This test will ensure that if multiple SurfaceSyncGroups are crated for the same ViewRootImpl
130      * the SurfaceSyncGroups will maintain an order. The scenario that could occur is the following:
131      * 1. SSG1 is created that includes the target VRI. There could be other VRIs in SSG1
132      * 2. target VRI draws its frame and is ready, but SSG1 is still waiting on other things in the
133      * SSG
134      * 3. Another SSG2 is created for the target VRI. The second frame renders and is ready and this
135      * SSG has nothing else to wait on. SSG2 will apply at this point, even though SSG1 has not
136      * 4. Frame2 will get to SF first and possibly later Frame1 will get to SF when SSG1 completes.
137      * <p>
138      * This test forces that behavior by ensuring the first SSG does not complete until the second
139      * SSG gets its buffer and is considered complete. It waits a bit to ensure it had time to
140      * make it to SF first. Then later it marks the first SSG as ready so that buffer could get
141      * sent to SF.
142      * <p>
143      * With the fix in VRI, the second SSG will not complete until the first SSG has been submitted
144      * to SF, ensuring that the frames are submitted in order.
145      */
146     @Test
testOverlappingSyncsEnsureOrder()147     public void testOverlappingSyncsEnsureOrder() throws InterruptedException {
148         CountDownLatch secondDrawCompleteLatch = new CountDownLatch(1);
149         CountDownLatch bothSyncGroupsComplete = new CountDownLatch(2);
150         final SurfaceSyncGroup firstSsg = new SurfaceSyncGroup(TAG + "-first");
151         final SurfaceSyncGroup secondSsg = new SurfaceSyncGroup(TAG + "-second");
152 
153         View backgroundView = mActivity.getBackgroundView();
154         mActivity.runOnUiThread(() -> {
155             firstSsg.add(backgroundView.getRootSurfaceControl(),
156                     () -> backgroundView.setBackgroundColor(Color.RED));
157             addSecondSyncGroup(secondSsg, secondDrawCompleteLatch, bothSyncGroupsComplete);
158         });
159 
160         assertTrue("Failed to draw two frames", secondDrawCompleteLatch.await(5, TimeUnit.SECONDS));
161 
162         // Add a bit of a delay to make sure the second frame could have been sent to SF
163         Thread.sleep(HW_TIMEOUT_MULTIPLIER * 32L);
164 
165         SurfaceControl.Transaction t = new SurfaceControl.Transaction();
166         t.addTransactionCommittedListener(Runnable::run, bothSyncGroupsComplete::countDown);
167         firstSsg.addTransaction(t);
168         firstSsg.markSyncReady();
169 
170         assertTrue("Failed to wait for both SurfaceSyncGroups to apply",
171                 bothSyncGroupsComplete.await(HW_TIMEOUT_MULTIPLIER * 5L, TimeUnit.SECONDS));
172 
173         validateScreenshot();
174     }
175 
addSecondSyncGroup(SurfaceSyncGroup surfaceSyncGroup, CountDownLatch waitForSecondDraw, CountDownLatch bothSyncGroupsComplete)176     private void addSecondSyncGroup(SurfaceSyncGroup surfaceSyncGroup,
177             CountDownLatch waitForSecondDraw, CountDownLatch bothSyncGroupsComplete) {
178         View backgroundView = mActivity.getBackgroundView();
179         ViewTreeObserver viewTreeObserver = backgroundView.getViewTreeObserver();
180         viewTreeObserver.registerFrameCommitCallback(() -> mActivity.runOnUiThread(() -> {
181             surfaceSyncGroup.add(backgroundView.getRootSurfaceControl(),
182                     () -> backgroundView.setBackgroundColor(Color.BLUE));
183             SurfaceControl.Transaction t = new SurfaceControl.Transaction();
184             t.addTransactionCommittedListener(Runnable::run, bothSyncGroupsComplete::countDown);
185             surfaceSyncGroup.addTransaction(t);
186             surfaceSyncGroup.markSyncReady();
187 
188             viewTreeObserver.registerFrameCommitCallback(waitForSecondDraw::countDown);
189         }));
190     }
191 
validateScreenshot()192     private void validateScreenshot() {
193         Bitmap screenshot = mInstrumentation.getUiAutomation().takeScreenshot(
194                 mActivity.getWindow());
195         assertNotNull("Failed to generate a screenshot", screenshot);
196         Bitmap swBitmap = screenshot.copy(Bitmap.Config.ARGB_8888, false);
197         screenshot.recycle();
198 
199         BitmapPixelChecker pixelChecker = new BitmapPixelChecker(Color.BLUE);
200         int halfWidth = swBitmap.getWidth() / 2;
201         int halfHeight = swBitmap.getHeight() / 2;
202         // We don't need to check all the pixels since we only care that at least some of them are
203         // blue. If the buffers were submitted out of order, all the pixels will be red.
204         Rect bounds = new Rect(halfWidth, halfHeight, halfWidth + 10, halfHeight + 10);
205         int numMatchingPixels = pixelChecker.getNumMatchingPixels(swBitmap, bounds);
206         assertEquals("Expected 100 received " + numMatchingPixels + " matching pixels", 100,
207                 numMatchingPixels);
208 
209         swBitmap.recycle();
210     }
211 }
212