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 com.android.server.wm;
18 
19 import static android.os.Build.HW_TIMEOUT_MULTIPLIER;
20 import static android.window.SurfaceSyncGroup.TRANSACTION_READY_TIMEOUT;
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.Instrumentation;
27 import android.graphics.Bitmap;
28 import android.graphics.Color;
29 import android.graphics.PixelFormat;
30 import android.graphics.Rect;
31 import android.os.Handler;
32 import android.os.HandlerThread;
33 import android.platform.test.annotations.Presubmit;
34 import android.server.wm.scvh.SurfaceSyncGroupActivity;
35 import android.view.SurfaceControl;
36 import android.view.View;
37 import android.view.ViewTreeObserver;
38 import android.view.WindowManager;
39 import android.view.cts.surfacevalidator.BitmapPixelChecker;
40 import android.window.SurfaceSyncGroup;
41 
42 import androidx.test.InstrumentationRegistry;
43 import androidx.test.rule.ActivityTestRule;
44 
45 import org.junit.Before;
46 import org.junit.Rule;
47 import org.junit.Test;
48 
49 import java.util.concurrent.CountDownLatch;
50 import java.util.concurrent.TimeUnit;
51 
52 @Presubmit
53 public class SurfaceSyncGroupTests {
54     private static final String TAG = "SurfaceSyncGroupTests";
55 
56     private static final long TIMEOUT_S = HW_TIMEOUT_MULTIPLIER * 5L;
57 
58     @Rule
59     public ActivityTestRule<SurfaceSyncGroupActivity> mActivityRule = new ActivityTestRule<>(
60             SurfaceSyncGroupActivity.class);
61 
62     private SurfaceSyncGroupActivity mActivity;
63 
64     Instrumentation mInstrumentation;
65 
66     private final HandlerThread mHandlerThread = new HandlerThread("applyTransaction");
67     private Handler mHandler;
68 
69     @Before
setup()70     public void setup() {
71         mActivity = mActivityRule.getActivity();
72         mInstrumentation = InstrumentationRegistry.getInstrumentation();
73         mHandlerThread.start();
74         mHandler = mHandlerThread.getThreadHandler();
75     }
76 
77     @Test
testOverlappingSyncsEnsureOrder_WhenTimeout()78     public void testOverlappingSyncsEnsureOrder_WhenTimeout() throws InterruptedException {
79         WindowManager.LayoutParams params = new WindowManager.LayoutParams();
80         params.format = PixelFormat.TRANSLUCENT;
81 
82         CountDownLatch secondDrawCompleteLatch = new CountDownLatch(1);
83         CountDownLatch bothSyncGroupsComplete = new CountDownLatch(2);
84         final SurfaceSyncGroup firstSsg = new SurfaceSyncGroup(TAG + "-first");
85         final SurfaceSyncGroup secondSsg = new SurfaceSyncGroup(TAG + "-second");
86         final SurfaceSyncGroup infiniteSsg = new SurfaceSyncGroup(TAG + "-infinite");
87 
88         SurfaceControl.Transaction t = new SurfaceControl.Transaction();
89         t.addTransactionCommittedListener(Runnable::run, bothSyncGroupsComplete::countDown);
90         firstSsg.addTransaction(t);
91 
92         View backgroundView = mActivity.getBackgroundView();
93         firstSsg.add(backgroundView.getRootSurfaceControl(),
94                 () -> mActivity.runOnUiThread(() -> backgroundView.setBackgroundColor(Color.RED)));
95 
96         addSecondSyncGroup(secondSsg, secondDrawCompleteLatch, bothSyncGroupsComplete);
97 
98         assertTrue("Failed to draw two frames",
99                 secondDrawCompleteLatch.await(TIMEOUT_S, TimeUnit.SECONDS));
100 
101         mHandler.postDelayed(() -> {
102             // Don't add a markSyncReady for the first sync group until after it's added to another
103             // SSG to ensure the timeout is longer than the second frame's timeout. The infinite SSG
104             // will never complete to ensure it reaches the timeout, but only after the second SSG
105             // had a chance to reach its timeout.
106             infiniteSsg.add(firstSsg, null /* runnable */);
107             firstSsg.markSyncReady();
108         }, 200);
109 
110         assertTrue("Failed to wait for both SurfaceSyncGroups to apply",
111                 bothSyncGroupsComplete.await(TIMEOUT_S, TimeUnit.SECONDS));
112 
113         validateScreenshot();
114     }
115 
116     @Test
testOverlappingSyncsEnsureOrder_WhileHoldingTransaction()117     public void testOverlappingSyncsEnsureOrder_WhileHoldingTransaction()
118             throws InterruptedException {
119         WindowManager.LayoutParams params = new WindowManager.LayoutParams();
120         params.format = PixelFormat.TRANSLUCENT;
121 
122         CountDownLatch secondDrawCompleteLatch = new CountDownLatch(1);
123         CountDownLatch bothSyncGroupsComplete = new CountDownLatch(2);
124 
125         final SurfaceSyncGroup firstSsg = new SurfaceSyncGroup(TAG + "-first",
126                 transaction -> mHandler.postDelayed(() -> {
127                     try {
128                         assertTrue("Failed to draw two frames",
129                                 secondDrawCompleteLatch.await(TIMEOUT_S, TimeUnit.SECONDS));
130                     } catch (InterruptedException e) {
131                         throw new RuntimeException(e);
132                     }
133                     transaction.apply();
134                 }, TRANSACTION_READY_TIMEOUT + 200));
135         final SurfaceSyncGroup secondSsg = new SurfaceSyncGroup(TAG + "-second");
136 
137         SurfaceControl.Transaction t = new SurfaceControl.Transaction();
138         t.addTransactionCommittedListener(Runnable::run, bothSyncGroupsComplete::countDown);
139         firstSsg.addTransaction(t);
140 
141         View backgroundView = mActivity.getBackgroundView();
142         firstSsg.add(backgroundView.getRootSurfaceControl(),
143                 () -> mActivity.runOnUiThread(() -> backgroundView.setBackgroundColor(Color.RED)));
144         firstSsg.markSyncReady();
145 
146         addSecondSyncGroup(secondSsg, secondDrawCompleteLatch, bothSyncGroupsComplete);
147 
148         assertTrue("Failed to wait for both SurfaceSyncGroups to apply",
149                 bothSyncGroupsComplete.await(TIMEOUT_S, TimeUnit.SECONDS));
150 
151         validateScreenshot();
152     }
153 
addSecondSyncGroup(SurfaceSyncGroup surfaceSyncGroup, CountDownLatch waitForSecondDraw, CountDownLatch bothSyncGroupsComplete)154     private void addSecondSyncGroup(SurfaceSyncGroup surfaceSyncGroup,
155             CountDownLatch waitForSecondDraw, CountDownLatch bothSyncGroupsComplete) {
156         View backgroundView = mActivity.getBackgroundView();
157         ViewTreeObserver viewTreeObserver = backgroundView.getViewTreeObserver();
158         viewTreeObserver.registerFrameCommitCallback(() -> mHandler.post(() -> {
159             surfaceSyncGroup.add(backgroundView.getRootSurfaceControl(),
160                     () -> mActivity.runOnUiThread(
161                             () -> backgroundView.setBackgroundColor(Color.BLUE)));
162 
163             SurfaceControl.Transaction t = new SurfaceControl.Transaction();
164             t.addTransactionCommittedListener(Runnable::run, bothSyncGroupsComplete::countDown);
165             surfaceSyncGroup.addTransaction(t);
166             surfaceSyncGroup.markSyncReady();
167             viewTreeObserver.registerFrameCommitCallback(waitForSecondDraw::countDown);
168         }));
169     }
170 
validateScreenshot()171     private void validateScreenshot() {
172         Bitmap screenshot = mInstrumentation.getUiAutomation().takeScreenshot(
173                 mActivity.getWindow());
174         assertNotNull("Failed to generate a screenshot", screenshot);
175         Bitmap swBitmap = screenshot.copy(Bitmap.Config.ARGB_8888, false);
176         screenshot.recycle();
177 
178         BitmapPixelChecker pixelChecker = new BitmapPixelChecker(Color.BLUE);
179         int halfWidth = swBitmap.getWidth() / 2;
180         int halfHeight = swBitmap.getHeight() / 2;
181         // We don't need to check all the pixels since we only care that at least some of them are
182         // blue. If the buffers were submitted out of order, all the pixels will be red.
183         Rect bounds = new Rect(halfWidth, halfHeight, halfWidth + 10, halfHeight + 10);
184         int numMatchingPixels = pixelChecker.getNumMatchingPixels(swBitmap, bounds);
185         assertEquals("Expected 100 received " + numMatchingPixels + " matching pixels", 100,
186                 numMatchingPixels);
187 
188         swBitmap.recycle();
189     }
190 }
191