1 /* 2 * Copyright (C) 2016 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.layoutlib.bridge.intensive; 18 19 import com.android.ide.common.rendering.api.ILayoutLog; 20 import com.android.ide.common.rendering.api.RenderSession; 21 import com.android.ide.common.rendering.api.Result; 22 import com.android.ide.common.rendering.api.SessionParams; 23 import com.android.ide.common.rendering.api.SessionParams.RenderingMode; 24 import com.android.ide.common.resources.deprecated.FrameworkResources; 25 import com.android.ide.common.resources.deprecated.ResourceItem; 26 import com.android.ide.common.resources.deprecated.ResourceRepository; 27 import com.android.ide.common.resources.deprecated.TestFolderWrapper; 28 import com.android.internal.lang.System_Delegate; 29 import com.android.layoutlib.bridge.Bridge; 30 import com.android.layoutlib.bridge.android.RenderParamsFlags; 31 import com.android.layoutlib.bridge.intensive.setup.ConfigGenerator; 32 import com.android.layoutlib.bridge.intensive.setup.LayoutPullParser; 33 import com.android.layoutlib.bridge.intensive.setup.LayoutlibBridgeClientCallback; 34 import com.android.layoutlib.bridge.intensive.util.ImageUtils; 35 import com.android.layoutlib.bridge.intensive.util.ModuleClassLoader; 36 import com.android.layoutlib.bridge.intensive.util.SessionParamsBuilder; 37 import com.android.layoutlib.bridge.intensive.util.TestAssetRepository; 38 import com.android.utils.ILogger; 39 40 import org.junit.AfterClass; 41 import org.junit.Before; 42 import org.junit.BeforeClass; 43 import org.junit.Rule; 44 import org.junit.rules.TestWatcher; 45 import org.junit.runner.Description; 46 47 import android.annotation.NonNull; 48 import android.annotation.Nullable; 49 import android.view.Choreographer; 50 51 import java.awt.image.BufferedImage; 52 import java.io.File; 53 import java.io.FileNotFoundException; 54 import java.io.IOException; 55 import java.util.ArrayList; 56 import java.util.Arrays; 57 import java.util.concurrent.TimeUnit; 58 59 import com.google.android.collect.Lists; 60 61 import static org.junit.Assert.assertNotNull; 62 import static org.junit.Assert.fail; 63 64 /** 65 * Base class for render tests. The render tests load all the framework resources and a project 66 * checked in this test's resources. The main dependencies 67 * are: 68 * 1. Fonts directory. 69 * 2. Framework Resources. 70 * 3. App resources. 71 * 4. build.prop file 72 * <p> 73 * These are configured by two variables set in the system properties. 74 * <p> 75 * 1. platform.dir: This is the directory for the current platform in the built SDK 76 * (.../sdk/platforms/android-<version>). 77 * <p> 78 * The fonts are platform.dir/data/fonts. 79 * The Framework resources are platform.dir/data/res. 80 * build.prop is at platform.dir/build.prop. 81 * <p> 82 * 2. test_res.dir: This is the directory for the resources of the test. If not specified, this 83 * falls back to getClass().getProtectionDomain().getCodeSource().getLocation() 84 * <p> 85 * The app resources are obtained using getAppResources() getAppTestAsset() getAppClassesLocation() 86 */ 87 public abstract class BridgeClient { 88 89 protected static final String PLATFORM_DIR; 90 private static final String ANDROID_HOST_OUT_DIR_PROPERTY = "android_host_out.dir"; 91 private static final String NATIVE_LIB_PATH_PROPERTY = "native.lib.path"; 92 private static final String FONT_DIR_PROPERTY = "font.dir"; 93 private static final String ICU_DATA_PATH_PROPERTY = "icu.data.path"; 94 private static final String KEYBOARD_DIR_PROPERTY = "keyboard.dir"; 95 private static final String PLATFORM_DIR_PROPERTY = "platform.dir"; 96 97 private static final String NATIVE_LIB_DIR_PATH; 98 private static final String FONT_DIR; 99 private static final String ICU_DATA_PATH; 100 private static final String KEYBOARD_DIR; 101 private static final String EMPTY_FRAME = 102 "<?xml version=\"1.0\" encoding=\"utf-8\"?> <FrameLayout " 103 + "xmlns:android=\"http://schemas.android.com/apk/res/android\" " 104 + "android:layout_width=\"match_parent\" " 105 + "android:layout_height=\"match_parent\"> </FrameLayout>"; 106 protected static Bridge sBridge; 107 /** List of log messages generated by a render call. It can be used to find specific errors */ 108 protected static ArrayList<String> sRenderMessages = Lists.newArrayList(); 109 private static ILayoutLog sLayoutLibLog; 110 private static FrameworkResources sFrameworkRepo; 111 private static ResourceRepository sProjectResources; 112 private static ILogger sLogger; 113 114 static { 115 // Test that System Properties are properly set. 116 PLATFORM_DIR = getPlatformDir(); 117 if (PLATFORM_DIR == null) { 118 fail(String.format("System Property %1$s not properly set. The value is %2$s", 119 PLATFORM_DIR_PROPERTY, System.getProperty(PLATFORM_DIR_PROPERTY))); 120 } 121 122 NATIVE_LIB_DIR_PATH = getNativeLibDirPath(); 123 FONT_DIR = getFontDir(); 124 ICU_DATA_PATH = getIcuDataPath(); 125 KEYBOARD_DIR = getKeyboardDir(); 126 } 127 128 @Rule 129 public TestWatcher sRenderMessageWatcher = new TestWatcher() { 130 @Override 131 protected void succeeded(Description description) { 132 // We only check error messages if the rest of the test case was successful. 133 if (!sRenderMessages.isEmpty()) { 134 fail(description.getMethodName() + " render error message: " + 135 sRenderMessages.get(0)); 136 } 137 } 138 }; 139 @Rule 140 public TestWatcher sMemoryLeakChecker = new TestWatcher() { 141 @Override 142 protected void succeeded(Description description) { 143 for (int i = Choreographer.CALLBACK_INPUT; i <= Choreographer.CALLBACK_COMMIT; ++i) { 144 if (Choreographer.getInstance().mCallbackQueues[i].mHead != null) { 145 fail("Memory leak: leftover frame callbacks are detected in Choreographer"); 146 } 147 } 148 } 149 }; 150 protected ClassLoader mDefaultClassLoader; 151 private String mPackageName; 152 private String mThemeName; 153 BridgeClient(String themeName)154 public BridgeClient(String themeName) { 155 init(themeName); 156 } 157 BridgeClient()158 public BridgeClient() { 159 init("AppTheme"); 160 } 161 getNativeLibDirPath()162 private static String getNativeLibDirPath() { 163 String nativeLibDirPath = System.getProperty(NATIVE_LIB_PATH_PROPERTY); 164 if (nativeLibDirPath != null) { 165 File nativeLibDir = new File(nativeLibDirPath); 166 if (nativeLibDir.isDirectory()) { 167 nativeLibDirPath = nativeLibDir.getAbsolutePath(); 168 } else { 169 nativeLibDirPath = null; 170 } 171 } 172 if (nativeLibDirPath == null) { 173 nativeLibDirPath = PLATFORM_DIR + "/../../../../../lib64/"; 174 } 175 return nativeLibDirPath; 176 } 177 getFontDir()178 private static String getFontDir() { 179 String fontDir = System.getProperty(FONT_DIR_PROPERTY); 180 if (fontDir == null) { 181 // The fonts are built into out/host/common/obj/PACKAGING/fonts_intermediates 182 // as specified in build/make/core/layoutlib_data.mk, and PLATFORM_DIR is 183 // out/host/[arch]/sdk/sdk*/android-sdk*/platforms/android* 184 fontDir = PLATFORM_DIR + 185 "/../../../../../../common/obj/PACKAGING/fonts_intermediates"; 186 } 187 return fontDir; 188 } 189 getIcuDataPath()190 private static String getIcuDataPath() { 191 String icuDataPath = System.getProperty(ICU_DATA_PATH_PROPERTY); 192 if (icuDataPath == null) { 193 icuDataPath = PLATFORM_DIR + "/../../../../../com.android.i18n/etc/icu/icudt72l.dat"; 194 } 195 return icuDataPath; 196 } 197 getKeyboardDir()198 private static String getKeyboardDir() { 199 String keyboardDir = System.getProperty(KEYBOARD_DIR_PROPERTY); 200 if (keyboardDir == null) { 201 // The keyboard files are built into 202 // out/host/common/obj/PACKAGING/keyboards_intermediates 203 // as specified in build/make/core/layoutlib_data.mk, and PLATFORM_DIR is 204 // out/host/[arch]/sdk/sdk*/android-sdk*/platforms/android* 205 keyboardDir = PLATFORM_DIR + 206 "/../../../../../../common/obj/PACKAGING/keyboards_intermediates"; 207 } 208 return keyboardDir; 209 } 210 getAndroidHostOutDir()211 private static String getAndroidHostOutDir() { 212 String androidHostOutDir = System.getProperty(ANDROID_HOST_OUT_DIR_PROPERTY); 213 if (androidHostOutDir != null && !androidHostOutDir.isEmpty() 214 && new File(androidHostOutDir).isDirectory()) { 215 return androidHostOutDir; 216 } 217 218 androidHostOutDir = System.getenv("ANDROID_HOST_OUT"); 219 if (androidHostOutDir != null && !androidHostOutDir.isEmpty() 220 && new File(androidHostOutDir).isDirectory()) { 221 return androidHostOutDir; 222 } 223 224 return null; 225 } 226 getPlatformDir()227 private static String getPlatformDir() { 228 String platformDir = System.getProperty(PLATFORM_DIR_PROPERTY); 229 if (platformDir != null && !platformDir.isEmpty() && new File(platformDir).isDirectory()) { 230 return platformDir; 231 } 232 // System Property not set. Try to find the directory in the build directory. 233 String androidHostOut = getAndroidHostOutDir(); 234 if (androidHostOut != null) { 235 platformDir = getPlatformDirFromHostOut(new File(androidHostOut)); 236 if (platformDir != null) { 237 return platformDir; 238 } 239 } 240 String workingDirString = System.getProperty("user.dir"); 241 File workingDir = new File(workingDirString); 242 // Test if workingDir is android checkout root. 243 platformDir = getPlatformDirFromRoot(workingDir); 244 if (platformDir != null) { 245 return platformDir; 246 } 247 248 // Test if workingDir is platform/frameworks/base/tools/layoutlib/bridge. 249 File currentDir = workingDir; 250 if (currentDir.getName().equalsIgnoreCase("bridge")) { 251 currentDir = currentDir.getParentFile(); 252 } 253 254 // Find frameworks/layoutlib 255 while (currentDir != null && !"layoutlib".equals(currentDir.getName())) { 256 currentDir = currentDir.getParentFile(); 257 } 258 259 if (currentDir == null || 260 currentDir.getParentFile() == null || 261 !"frameworks".equals(currentDir.getParentFile().getName())) { 262 return null; 263 } 264 265 // Test if currentDir is platform/frameworks/layoutlib. That is, root should be 266 // workingDir/../../ (2 levels up) 267 for (int i = 0; i < 2; i++) { 268 if (currentDir != null) { 269 currentDir = currentDir.getParentFile(); 270 } 271 } 272 return currentDir == null ? null : getPlatformDirFromRoot(currentDir); 273 } 274 getPlatformDirFromRoot(File root)275 private static String getPlatformDirFromRoot(File root) { 276 if (!root.isDirectory()) { 277 return null; 278 } 279 File out = new File(root, "out"); 280 if (!out.isDirectory()) { 281 return null; 282 } 283 File host = new File(out, "host"); 284 if (!host.isDirectory()) { 285 return null; 286 } 287 File[] hosts = host.listFiles(path -> path.isDirectory() && 288 (path.getName().startsWith("linux-") || 289 path.getName().startsWith("darwin-"))); 290 assert hosts != null; 291 for (File hostOut : hosts) { 292 String platformDir = getPlatformDirFromHostOut(hostOut); 293 if (platformDir != null) { 294 return platformDir; 295 } 296 } 297 298 return null; 299 } 300 getPlatformDirFromHostOut(File out)301 private static String getPlatformDirFromHostOut(File out) { 302 if (!out.isDirectory()) { 303 return null; 304 } 305 File sdkDir = new File(out, "sdk"); 306 if (!sdkDir.isDirectory()) { 307 return null; 308 } 309 File[] sdkDirs = sdkDir.listFiles(path -> { 310 // We need to search for $TARGET_PRODUCT (usually, sdk_phone_armv7) 311 return path.isDirectory() && path.getName().startsWith("sdk"); 312 }); 313 assert sdkDirs != null; 314 for (File dir : sdkDirs) { 315 String platformDir = getPlatformDirFromHostOutSdkSdk(dir); 316 if (platformDir != null) { 317 return platformDir; 318 } 319 } 320 return null; 321 } 322 getPlatformDirFromHostOutSdkSdk(File sdkDir)323 private static String getPlatformDirFromHostOutSdkSdk(File sdkDir) { 324 File[] possibleSdks = sdkDir.listFiles( 325 path -> path.isDirectory() && path.getName().contains("android-sdk")); 326 assert possibleSdks != null; 327 for (File possibleSdk : possibleSdks) { 328 File platformsDir = new File(possibleSdk, "platforms"); 329 File[] platforms = platformsDir.listFiles( 330 path -> path.isDirectory() && path.getName().startsWith("android-")); 331 if (platforms == null || platforms.length == 0) { 332 continue; 333 } 334 Arrays.sort(platforms, (o1, o2) -> { 335 final int MAX_VALUE = 1000; 336 String suffix1 = o1.getName().substring("android-".length()); 337 String suffix2 = o2.getName().substring("android-".length()); 338 int suff1, suff2; 339 try { 340 suff1 = Integer.parseInt(suffix1); 341 } catch (NumberFormatException e) { 342 suff1 = MAX_VALUE; 343 } 344 try { 345 suff2 = Integer.parseInt(suffix2); 346 } catch (NumberFormatException e) { 347 suff2 = MAX_VALUE; 348 } 349 if (suff1 != MAX_VALUE || suff2 != MAX_VALUE) { 350 return suff2 - suff1; 351 } 352 return suffix2.compareTo(suffix1); 353 }); 354 return platforms[0].getAbsolutePath(); 355 } 356 return null; 357 } 358 359 /** 360 * Initialize the bridge and the resource maps. 361 */ 362 @BeforeClass beforeClass()363 public static void beforeClass() { 364 File data_dir = new File(PLATFORM_DIR, "data"); 365 File res = new File(data_dir, "res"); 366 sFrameworkRepo = new FrameworkResources(new TestFolderWrapper(res)); 367 sFrameworkRepo.loadResources(); 368 sFrameworkRepo.loadPublicResources(getLogger()); 369 370 File fontLocation = new File(FONT_DIR); 371 File buildProp = new File(PLATFORM_DIR, "build.prop"); 372 File attrs = new File(res, "values" + File.separator + "attrs.xml"); 373 String[] keyboardPaths = new String[0]; 374 sBridge = new Bridge(); 375 sBridge.init(ConfigGenerator.loadProperties(buildProp), fontLocation, NATIVE_LIB_DIR_PATH, 376 ICU_DATA_PATH, keyboardPaths, ConfigGenerator.getEnumMap(attrs), getLayoutLog()); 377 Bridge.getLock().lock(); 378 try { 379 Bridge.setLog(getLayoutLog()); 380 } finally { 381 Bridge.getLock().unlock(); 382 } 383 } 384 385 @AfterClass tearDown()386 public static void tearDown() { 387 sLayoutLibLog = null; 388 sFrameworkRepo = null; 389 sProjectResources = null; 390 sLogger = null; 391 sBridge = null; 392 } 393 getLayoutLog()394 protected static ILayoutLog getLayoutLog() { 395 if (sLayoutLibLog == null) { 396 sLayoutLibLog = new ILayoutLog() { 397 @Override 398 public void warning(@Nullable String tag, @NonNull String message, 399 @Nullable Object viewCookie, 400 @Nullable Object data) { 401 System.out.println("BridgeClient: LayoutLog: Warning " + tag + ": " + message); 402 failWithMsg(message); 403 } 404 405 @Override 406 public void fidelityWarning(@Nullable String tag, String message, 407 Throwable throwable, Object cookie, Object data) { 408 409 System.out.println("FidelityWarning " + tag + ": " + message); 410 if (throwable != null) { 411 throwable.printStackTrace(); 412 } 413 failWithMsg(message == null ? "" : message); 414 } 415 416 @Override 417 public void error(@Nullable String tag, @NonNull String message, 418 @Nullable Object viewCookie, 419 @Nullable Object data) { 420 System.out.println("Error " + tag + ": " + message); 421 failWithMsg(message); 422 } 423 424 @Override 425 public void error(@Nullable String tag, @NonNull String message, 426 @Nullable Throwable throwable, 427 @Nullable Object viewCookie, @Nullable Object data) { 428 System.out.println("Error " + tag + ": " + message); 429 if (throwable != null) { 430 throwable.printStackTrace(); 431 } 432 failWithMsg(message); 433 } 434 435 @Override 436 public void logAndroidFramework(int priority, String tag, String message) { 437 System.out.println("Android framework message " + tag + ": " + message); 438 } 439 }; 440 } 441 return sLayoutLibLog; 442 } 443 ignoreAllLogging()444 protected static void ignoreAllLogging() { 445 sLayoutLibLog = new ILayoutLog() { 446 }; 447 sLogger = new ILogger() { 448 @Override 449 public void error(Throwable t, String msgFormat, Object... args) { 450 } 451 452 @Override 453 public void warning(String msgFormat, Object... args) { 454 } 455 456 @Override 457 public void info(String msgFormat, Object... args) { 458 } 459 460 @Override 461 public void verbose(String msgFormat, Object... args) { 462 } 463 }; 464 } 465 getLogger()466 protected static ILogger getLogger() { 467 if (sLogger == null) { 468 sLogger = new ILogger() { 469 @Override 470 public void error(Throwable t, @Nullable String msgFormat, Object... args) { 471 if (t != null) { 472 t.printStackTrace(); 473 } 474 failWithMsg(msgFormat == null ? "" : msgFormat, args); 475 } 476 477 @Override 478 public void warning(@NonNull String msgFormat, Object... args) { 479 failWithMsg(msgFormat, args); 480 } 481 482 @Override 483 public void info(@NonNull String msgFormat, Object... args) { 484 // pass. 485 } 486 487 @Override 488 public void verbose(@NonNull String msgFormat, Object... args) { 489 // pass. 490 } 491 }; 492 } 493 return sLogger; 494 } 495 failWithMsg(@onNull String msgFormat, Object... args)496 private static void failWithMsg(@NonNull String msgFormat, Object... args) { 497 sRenderMessages.add(args == null ? msgFormat : String.format(msgFormat, args)); 498 } 499 getAppTestDir()500 public abstract String getAppTestDir(); 501 getAppTestRes()502 public abstract String getAppTestRes(); 503 getAppResources()504 public abstract String getAppResources(); 505 getAppGoldenDir()506 public abstract String getAppGoldenDir(); 507 getAppTestAsset()508 public abstract String getAppTestAsset(); 509 getAppClassesLocation()510 public abstract String getAppClassesLocation(); 511 init(String themeName)512 protected void init(String themeName) { 513 mPackageName = "NOT_INITIALIZED"; 514 mThemeName = themeName; 515 initProjectResources(); 516 } 517 setPackageName(String packageName)518 public void setPackageName(String packageName) { 519 mPackageName = packageName; 520 } 521 initProjectResources()522 public void initProjectResources() { 523 String TEST_RESOURCE_FOLDER = getAppTestRes(); 524 sProjectResources = 525 new ResourceRepository(new TestFolderWrapper(TEST_RESOURCE_FOLDER), false) { 526 @NonNull 527 @Override 528 protected ResourceItem createResourceItem(@NonNull String name) { 529 return new ResourceItem(name); 530 } 531 }; 532 sProjectResources.loadResources(); 533 534 } 535 536 @NonNull render(com.android.ide.common.rendering.api.Bridge bridge, SessionParams params, long frameTimeNanos)537 protected RenderResult render(com.android.ide.common.rendering.api.Bridge bridge, 538 SessionParams params, 539 long frameTimeNanos) { 540 return render(bridge, params, frameTimeNanos, null); 541 } 542 543 @NonNull render(com.android.ide.common.rendering.api.Bridge bridge, SessionParams params, long frameTimeNanos, @Nullable RenderSessionListener listener)544 protected RenderResult render(com.android.ide.common.rendering.api.Bridge bridge, 545 SessionParams params, 546 long frameTimeNanos, 547 @Nullable RenderSessionListener listener) { 548 // TODO: Set up action bar handler properly to test menu rendering. 549 // Create session params. 550 System_Delegate.setBootTimeNanos(TimeUnit.MILLISECONDS.toNanos(871732800000L)); 551 System_Delegate.setNanosTime(TimeUnit.MILLISECONDS.toNanos(871732800000L)); 552 553 RenderSession session = bridge.createSession(params); 554 555 try { 556 if (frameTimeNanos != -1) { 557 session.setElapsedFrameTimeNanos(frameTimeNanos); 558 } 559 560 if (!session.getResult().isSuccess()) { 561 getLogger().error(session.getResult().getException(), 562 session.getResult().getErrorMessage()); 563 } else { 564 // Render the session with a timeout of 50s. 565 Result renderResult = session.render(50000); 566 if (!renderResult.isSuccess()) { 567 getLogger().error(session.getResult().getException(), 568 session.getResult().getErrorMessage()); 569 } 570 } 571 if (listener != null) { 572 listener.beforeDisposed(session); 573 } 574 575 return RenderResult.getFromSession(session); 576 } finally { 577 session.dispose(); 578 } 579 } 580 581 /** 582 * Compares the golden image with the passed image 583 */ verify(@onNull String goldenImageName, @NonNull BufferedImage image)584 protected void verify(@NonNull String goldenImageName, @NonNull BufferedImage image) { 585 try { 586 String goldenImagePath = getAppGoldenDir() + goldenImageName; 587 ImageUtils.requireSimilar(goldenImagePath, image); 588 } catch (IOException e) { 589 getLogger().error(e, e.getMessage()); 590 } 591 } 592 593 /** 594 * Create a new rendering session and test that rendering the given layout doesn't throw any 595 * exceptions and matches the provided image. 596 * <p> 597 * If frameTimeNanos is >= 0 a frame will be executed during the rendering. The time indicates 598 * how far in the future is. 599 */ 600 @Nullable renderAndVerify(SessionParams params, String goldenFileName, long frameTimeNanos)601 protected RenderResult renderAndVerify(SessionParams params, String goldenFileName, 602 long frameTimeNanos) throws ClassNotFoundException { 603 RenderResult result = render(sBridge, params, frameTimeNanos); 604 assertNotNull(result.getImage()); 605 verify(goldenFileName, result.getImage()); 606 607 return result; 608 } 609 610 /** 611 * Create a new rendering session and test that rendering the given layout doesn't throw any 612 * exceptions and matches the provided image. 613 */ 614 @Nullable renderAndVerify(SessionParams params, String goldenFileName)615 protected RenderResult renderAndVerify(SessionParams params, String goldenFileName) 616 throws ClassNotFoundException { 617 return renderAndVerify(params, goldenFileName, TimeUnit.SECONDS.toNanos(2)); 618 } 619 620 @Before beforeTestCase()621 public void beforeTestCase() { 622 // Default class loader with access to the app classes 623 mDefaultClassLoader = new ModuleClassLoader(getAppClassesLocation(), 624 getClass().getClassLoader()); 625 sRenderMessages.clear(); 626 } 627 628 @NonNull createParserFromPath(String layoutPath)629 protected LayoutPullParser createParserFromPath(String layoutPath) 630 throws FileNotFoundException { 631 return LayoutPullParser.createFromPath(getAppResources() + "/layout/" + layoutPath); 632 } 633 634 @NonNull createParserFromString(String layoutStr)635 protected LayoutPullParser createParserFromString(String layoutStr) 636 throws FileNotFoundException { 637 return LayoutPullParser.createFromString(layoutStr); 638 } 639 640 /** 641 * Create a new rendering session and test that rendering the given layout on nexus 5 642 * doesn't throw any exceptions and matches the provided image. 643 */ 644 @Nullable renderAndVerify(String layoutFileName, String goldenFileName, boolean decoration)645 protected RenderResult renderAndVerify(String layoutFileName, String goldenFileName, 646 boolean decoration) 647 throws ClassNotFoundException, FileNotFoundException { 648 return renderAndVerify(layoutFileName, goldenFileName, ConfigGenerator.NEXUS_5, decoration); 649 } 650 651 /** 652 * Create a new rendering session and test that rendering the given layout on given device 653 * doesn't throw any exceptions and matches the provided image. 654 */ 655 @Nullable renderAndVerify(String layoutFileName, String goldenFileName, ConfigGenerator deviceConfig, boolean decoration)656 protected RenderResult renderAndVerify(String layoutFileName, String goldenFileName, 657 ConfigGenerator deviceConfig, boolean decoration) throws ClassNotFoundException, 658 FileNotFoundException { 659 SessionParams params = createSessionParams(layoutFileName, deviceConfig); 660 if (!decoration) { 661 params.setForceNoDecor(); 662 } 663 return renderAndVerify(params, goldenFileName); 664 } 665 createSessionParams(String layoutFileName, ConfigGenerator deviceConfig)666 protected SessionParams createSessionParams(String layoutFileName, ConfigGenerator deviceConfig) 667 throws ClassNotFoundException, FileNotFoundException { 668 // Create the layout pull parser. 669 670 LayoutPullParser parser = null; 671 if (layoutFileName != null) { 672 parser = createParserFromPath(layoutFileName); 673 } else { 674 parser = createParserFromString(EMPTY_FRAME); 675 676 } 677 // Create LayoutLibCallback. 678 LayoutlibBridgeClientCallback layoutLibCallback = 679 new LayoutlibBridgeClientCallback(getLogger(), mDefaultClassLoader, mPackageName); 680 layoutLibCallback.initResources(); 681 // TODO: Set up action bar handler properly to test menu rendering. 682 // Create session params. 683 return getSessionParamsBuilder() 684 .setParser(parser) 685 .setConfigGenerator(deviceConfig) 686 .setCallback(layoutLibCallback) 687 .setTheme(mThemeName, true) 688 .build(); 689 } 690 691 /** 692 * Returns a pre-configured {@link SessionParamsBuilder} for target API 22, Normal rendering 693 * mode, AppTheme as theme and Nexus 5. 694 */ 695 @NonNull getSessionParamsBuilder()696 protected SessionParamsBuilder getSessionParamsBuilder() { 697 return new SessionParamsBuilder() 698 .setLayoutLog(getLayoutLog()) 699 .setFrameworkResources(sFrameworkRepo) 700 .setConfigGenerator(ConfigGenerator.NEXUS_5) 701 .setProjectResources(sProjectResources) 702 .setTheme("AppTheme", true) 703 .setRenderingMode(RenderingMode.NORMAL) 704 .setTargetSdk(28) 705 .setFlag(RenderParamsFlags.FLAG_DO_NOT_RENDER_ON_CREATE, true) 706 .setAssetRepository( 707 new TestAssetRepository(getAppTestAsset())); 708 } 709 710 /** 711 * Listener for render process. 712 */ 713 public interface RenderSessionListener { 714 715 /** 716 * Called before session is disposed after rendering. 717 */ beforeDisposed(RenderSession session)718 void beforeDisposed(RenderSession session); 719 } 720 } 721