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