1 /* 2 * Copyright (C) 2014 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 package android.uirendering.cts.testinfrastructure; 17 18 import android.app.Instrumentation; 19 import android.content.Intent; 20 import android.graphics.Bitmap; 21 import android.graphics.Bitmap.Config; 22 import android.graphics.Point; 23 import android.graphics.Rect; 24 import androidx.annotation.Nullable; 25 import android.support.test.InstrumentationRegistry; 26 import android.uirendering.cts.bitmapcomparers.BitmapComparer; 27 import android.uirendering.cts.bitmapverifiers.BitmapVerifier; 28 import android.uirendering.cts.util.BitmapAsserter; 29 import android.util.Log; 30 import android.view.PixelCopy; 31 32 import com.android.compatibility.common.util.SynchronousPixelCopy; 33 34 import org.junit.After; 35 import org.junit.AfterClass; 36 import org.junit.Assert; 37 import org.junit.Rule; 38 39 import java.util.ArrayList; 40 import java.util.List; 41 import java.util.concurrent.CountDownLatch; 42 import java.util.concurrent.TimeUnit; 43 44 /** 45 * This class contains the basis for the graphics hardware test classes. Contained within this class 46 * are several methods that help with the execution of tests, and should be extended to gain the 47 * functionality built in. 48 */ 49 public abstract class ActivityTestBase { 50 public static final String TAG = "ActivityTestBase"; 51 public static final boolean DEBUG = false; 52 53 //The minimum height and width of a device 54 public static final int TEST_WIDTH = 90; 55 public static final int TEST_HEIGHT = 90; 56 57 private TestCaseBuilder mTestCaseBuilder; 58 private Screenshotter mScreenshotter; 59 60 private static DrawActivity sActivity; 61 62 @Rule 63 public Tracer name = new Tracer(); 64 65 private BitmapAsserter mBitmapAsserter = new BitmapAsserter(this.getClass().getSimpleName(), 66 name.getMethodName()); 67 68 protected String getName() { 69 return name.getMethodName(); 70 } 71 72 protected Instrumentation getInstrumentation() { 73 return InstrumentationRegistry.getInstrumentation(); 74 } 75 76 protected DrawActivity getActivity() { 77 if (sActivity == null) { 78 Instrumentation instrumentation = getInstrumentation(); 79 instrumentation.setInTouchMode(true); 80 Intent intent = new Intent(Intent.ACTION_MAIN); 81 intent.setClass(instrumentation.getTargetContext(), DrawActivity.class); 82 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 83 intent.putExtra(DrawActivity.EXTRA_WIDE_COLOR_GAMUT, isWideColorGamut()); 84 sActivity = (DrawActivity) instrumentation.startActivitySync(intent); 85 } 86 return sActivity; 87 } 88 89 protected boolean isWideColorGamut() { 90 return false; 91 } 92 93 @AfterClass 94 public static void tearDownClass() { 95 if (sActivity != null) { 96 // All tests are finished, tear down the activity 97 sActivity.allTestsFinished(); 98 sActivity = null; 99 } 100 } 101 102 @After 103 public void tearDown() { 104 if (mTestCaseBuilder != null) { 105 List<TestCase> testCases = mTestCaseBuilder.getTestCases(); 106 107 if (testCases.size() == 0) { 108 throw new IllegalStateException("Must have at least one test case"); 109 } 110 111 for (TestCase testCase : testCases) { 112 if (!testCase.wasTestRan) { 113 Log.w(TAG, getName() + " not all of the tests ran"); 114 break; 115 } 116 } 117 mTestCaseBuilder = null; 118 } 119 } 120 121 public Bitmap takeScreenshot(TestPositionInfo testPositionInfo) { 122 if (mScreenshotter == null) { 123 SynchronousPixelCopy copy = new SynchronousPixelCopy(); 124 Bitmap dest = Bitmap.createBitmap( 125 TEST_WIDTH, TEST_HEIGHT, 126 getActivity().getWindow().isWideColorGamut() 127 ? Config.RGBA_F16 : Config.ARGB_8888); 128 Rect srcRect = new Rect(0, 0, TEST_WIDTH, TEST_HEIGHT); 129 srcRect.offset(testPositionInfo.surfaceOffset.x, testPositionInfo.surfaceOffset.y); 130 Log.d(TAG, "capturing screenshot of " + srcRect.toShortString()); 131 int copyResult = copy.request(getActivity().getWindow(), srcRect, dest); 132 Assert.assertEquals(PixelCopy.SUCCESS, copyResult); 133 return dest; 134 } else { 135 return mScreenshotter.takeScreenshot(testPositionInfo); 136 } 137 } 138 protected TestPositionInfo runRenderSpec(TestCase testCase) { 139 TestPositionInfo testPositionInfo = getActivity().enqueueRenderSpecAndWait( 140 testCase.layoutID, testCase.canvasClient, 141 testCase.viewInitializer, testCase.useHardware, testCase.usePicture); 142 testCase.wasTestRan = true; 143 if (testCase.readyFence != null) { 144 try { 145 testCase.readyFence.await(5, TimeUnit.SECONDS); 146 } catch (InterruptedException e) { 147 throw new RuntimeException("readyFence didn't signal within 5 seconds"); 148 } 149 } 150 return testPositionInfo; 151 } 152 153 /** 154 * Used to execute a specific part of a test and get the resultant bitmap 155 */ 156 protected Bitmap captureRenderSpec(TestCase testCase) { 157 return takeScreenshot(runRenderSpec(testCase)); 158 } 159 160 protected TestCaseBuilder createTest() { 161 mTestCaseBuilder = new TestCaseBuilder(); 162 mScreenshotter = null; 163 return mTestCaseBuilder; 164 } 165 166 public static class TestPositionInfo { 167 /** 168 * Position of capture area in surface space - use this offset for e.g. 169 * PixelCopy from a window's surface. 170 */ 171 public final Point surfaceOffset; 172 173 /** 174 * Position of capture area in screen space - use this offset for e.g. 175 * {@code getInstrumentation().getUiAutomation().takeScreenshot()}, 176 * since those screenshots are captured in screen space. 177 */ 178 public final Point screenOffset; 179 180 public TestPositionInfo(Point surfaceOffset, Point screenOffset) { 181 this.surfaceOffset = surfaceOffset; 182 this.screenOffset = screenOffset; 183 } 184 } 185 186 public interface Screenshotter { 187 Bitmap takeScreenshot(TestPositionInfo params); 188 } 189 190 /** 191 * Defines a group of CanvasClients, XML layouts, and WebView html files for testing. 192 */ 193 protected class TestCaseBuilder { 194 private List<TestCase> mTestCases; 195 196 private TestCaseBuilder() { 197 mTestCases = new ArrayList<>(); 198 } 199 200 /** 201 * Runs a test where the first test case is considered the "ideal" image and from there, 202 * every test case is tested against it. 203 */ 204 public void runWithComparer(BitmapComparer bitmapComparer) { 205 if (mTestCases.size() == 0) { 206 throw new IllegalStateException("Need at least one test to run"); 207 } 208 209 Bitmap idealBitmap = captureRenderSpec(mTestCases.remove(0)); 210 211 for (TestCase testCase : mTestCases) { 212 Bitmap testCaseBitmap = captureRenderSpec(testCase); 213 mBitmapAsserter.assertBitmapsAreSimilar(idealBitmap, testCaseBitmap, bitmapComparer, 214 getName(), testCase.getDebugString()); 215 } 216 } 217 218 /** 219 * Runs a test where each testcase is independent of the others and each is checked against 220 * the verifier given. 221 */ 222 public void runWithVerifier(BitmapVerifier bitmapVerifier) { 223 if (mTestCases.size() == 0) { 224 throw new IllegalStateException("Need at least one test to run"); 225 } 226 227 for (TestCase testCase : mTestCases) { 228 Bitmap testCaseBitmap = captureRenderSpec(testCase); 229 mBitmapAsserter.assertBitmapIsVerified(testCaseBitmap, bitmapVerifier, 230 getName(), testCase.getDebugString()); 231 } 232 getActivity().reset(); 233 } 234 235 private static final int VERIFY_ANIMATION_LOOP_COUNT = 20; 236 private static final int VERIFY_ANIMATION_SLEEP_MS = 100; 237 238 /** 239 * Runs a test where each testcase is independent of the others and each is checked against 240 * the verifier given in a loop. 241 * 242 * A screenshot is captured several times in a loop, to ensure that valid output is produced 243 * at many different times during the animation. 244 */ 245 public void runWithAnimationVerifier(BitmapVerifier bitmapVerifier) { 246 if (mTestCases.size() == 0) { 247 throw new IllegalStateException("Need at least one test to run"); 248 } 249 250 for (TestCase testCase : mTestCases) { 251 TestPositionInfo testPositionInfo = runRenderSpec(testCase); 252 253 for (int i = 0; i < VERIFY_ANIMATION_LOOP_COUNT; i++) { 254 try { 255 Thread.sleep(VERIFY_ANIMATION_SLEEP_MS); 256 } catch (InterruptedException e) { 257 e.printStackTrace(); 258 } 259 Bitmap testCaseBitmap = takeScreenshot(testPositionInfo); 260 mBitmapAsserter.assertBitmapIsVerified(testCaseBitmap, bitmapVerifier, 261 getName(), testCase.getDebugString()); 262 } 263 } 264 } 265 266 /** 267 * Runs a test where each testcase is run without verification. Should only be used 268 * where custom CanvasClients, Views, or ViewInitializers do their own internal 269 * test assertions. 270 */ 271 public void runWithoutVerification() { 272 runWithVerifier(new BitmapVerifier() { 273 @Override 274 public boolean verify(int[] bitmap, int offset, int stride, int width, int height) { 275 return true; 276 } 277 }); 278 } 279 280 public TestCaseBuilder withScreenshotter(Screenshotter screenshotter) { 281 Assert.assertNull("Screenshotter is already set!", mScreenshotter); 282 mScreenshotter = screenshotter; 283 return this; 284 } 285 286 public TestCaseBuilder addLayout(int layoutId, @Nullable ViewInitializer viewInitializer) { 287 return addLayout(layoutId, viewInitializer, false) 288 .addLayout(layoutId, viewInitializer, true); 289 } 290 291 public TestCaseBuilder addLayout(int layoutId, @Nullable ViewInitializer viewInitializer, 292 boolean useHardware) { 293 mTestCases.add(new TestCase(layoutId, viewInitializer, useHardware)); 294 return this; 295 } 296 297 public TestCaseBuilder addLayout(int layoutId, @Nullable ViewInitializer viewInitializer, 298 boolean useHardware, CountDownLatch readyFence) { 299 TestCase test = new TestCase(layoutId, viewInitializer, useHardware); 300 test.readyFence = readyFence; 301 mTestCases.add(test); 302 return this; 303 } 304 305 public TestCaseBuilder addCanvasClient(CanvasClient canvasClient) { 306 return addCanvasClient(null, canvasClient); 307 } 308 309 public TestCaseBuilder addCanvasClient(CanvasClient canvasClient, boolean useHardware) { 310 return addCanvasClient(null, canvasClient, useHardware); 311 } 312 313 public TestCaseBuilder addCanvasClient(String debugString, CanvasClient canvasClient) { 314 return addCanvasClient(debugString, canvasClient, false) 315 .addCanvasClient(debugString, canvasClient, true); 316 } 317 318 public TestCaseBuilder addCanvasClient(String debugString, 319 CanvasClient canvasClient, boolean useHardware) { 320 return addCanvasClientInternal(debugString, canvasClient, useHardware, false) 321 .addCanvasClientInternal(debugString, canvasClient, useHardware, true); 322 } 323 324 public TestCaseBuilder addCanvasClientWithoutUsingPicture(CanvasClient canvasClient) { 325 return addCanvasClientWithoutUsingPicture(null, canvasClient); 326 } 327 328 public TestCaseBuilder addCanvasClientWithoutUsingPicture(String debugString, 329 CanvasClient canvasClient) { 330 return addCanvasClientInternal(debugString, canvasClient, false, false) 331 .addCanvasClientInternal(debugString, canvasClient, true, false); 332 } 333 334 public TestCaseBuilder addCanvasClientWithoutUsingPicture(CanvasClient canvasClient, 335 boolean useHardware) { 336 return addCanvasClientInternal(null, canvasClient, useHardware, false); 337 } 338 339 private TestCaseBuilder addCanvasClientInternal(String debugString, 340 CanvasClient canvasClient, boolean useHardware, boolean usePicture) { 341 mTestCases.add(new TestCase(canvasClient, debugString, useHardware, usePicture)); 342 return this; 343 } 344 345 private List<TestCase> getTestCases() { 346 return mTestCases; 347 } 348 } 349 350 private class TestCase { 351 public int layoutID; 352 public ViewInitializer viewInitializer; 353 /** After launching the test case this fence is used to signal when 354 * to proceed with capture & verification. If this is null the test 355 * proceeds immediately to verification */ 356 @Nullable 357 public CountDownLatch readyFence; 358 359 public CanvasClient canvasClient; 360 public String canvasClientDebugString; 361 362 public boolean useHardware; 363 public boolean usePicture = false; 364 public boolean wasTestRan = false; 365 366 public TestCase(int layoutId, ViewInitializer viewInitializer, boolean useHardware) { 367 this.layoutID = layoutId; 368 this.viewInitializer = viewInitializer; 369 this.useHardware = useHardware; 370 } 371 372 public TestCase(CanvasClient client, String debugString, boolean useHardware, 373 boolean usePicture) { 374 this.canvasClient = client; 375 this.canvasClientDebugString = debugString; 376 this.useHardware = useHardware; 377 this.usePicture = usePicture; 378 } 379 380 public String getDebugString() { 381 String debug = ""; 382 if (canvasClient != null) { 383 debug += "CanvasClient : "; 384 if (canvasClientDebugString != null) { 385 debug += canvasClientDebugString; 386 } else { 387 debug += "no debug string given"; 388 } 389 } else { 390 debug += "Layout resource : " + 391 getActivity().getResources().getResourceName(layoutID); 392 } 393 debug += "\nTest ran in " + (useHardware ? "hardware" : "software") + 394 (usePicture ? " with picture" : " without picture") + "\n"; 395 return debug; 396 } 397 } 398 } 399