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 17package com.android.layoutlib.bridge.intensive; 18 19import com.android.ide.common.rendering.api.LayoutLog; 20import com.android.ide.common.rendering.api.RenderSession; 21import com.android.ide.common.rendering.api.Result; 22import com.android.ide.common.rendering.api.SessionParams; 23import com.android.ide.common.rendering.api.SessionParams.RenderingMode; 24import com.android.ide.common.rendering.api.ViewInfo; 25import com.android.ide.common.resources.FrameworkResources; 26import com.android.ide.common.resources.ResourceItem; 27import com.android.ide.common.resources.ResourceRepository; 28import com.android.ide.common.resources.ResourceResolver; 29import com.android.ide.common.resources.configuration.FolderConfiguration; 30import com.android.io.FolderWrapper; 31import com.android.layoutlib.bridge.Bridge; 32import com.android.layoutlib.bridge.android.BridgeContext; 33import com.android.layoutlib.bridge.android.RenderParamsFlags; 34import com.android.layoutlib.bridge.impl.DelegateManager; 35import com.android.layoutlib.bridge.impl.RenderAction; 36import com.android.layoutlib.bridge.intensive.setup.ConfigGenerator; 37import com.android.layoutlib.bridge.intensive.setup.LayoutLibTestCallback; 38import com.android.layoutlib.bridge.intensive.setup.LayoutPullParser; 39import com.android.resources.Density; 40import com.android.resources.Navigation; 41import com.android.resources.ResourceType; 42import com.android.utils.ILogger; 43 44import org.junit.AfterClass; 45import org.junit.Before; 46import org.junit.BeforeClass; 47import org.junit.Rule; 48import org.junit.Test; 49import org.junit.rules.TestWatcher; 50import org.junit.runner.Description; 51 52import android.annotation.NonNull; 53import android.annotation.Nullable; 54import android.content.res.AssetManager; 55import android.content.res.Configuration; 56import android.content.res.Resources; 57import android.util.DisplayMetrics; 58 59import java.io.File; 60import java.io.IOException; 61import java.lang.ref.WeakReference; 62import java.net.URL; 63import java.util.ArrayList; 64import java.util.Arrays; 65import java.util.concurrent.TimeUnit; 66 67import com.google.android.collect.Lists; 68 69import static org.junit.Assert.assertEquals; 70import static org.junit.Assert.assertNotNull; 71import static org.junit.Assert.assertTrue; 72import static org.junit.Assert.fail; 73 74/** 75 * This is a set of tests that loads all the framework resources and a project checked in this 76 * test's resources. The main dependencies 77 * are: 78 * 1. Fonts directory. 79 * 2. Framework Resources. 80 * 3. App resources. 81 * 4. build.prop file 82 * 83 * These are configured by two variables set in the system properties. 84 * 85 * 1. platform.dir: This is the directory for the current platform in the built SDK 86 * (.../sdk/platforms/android-<version>). 87 * 88 * The fonts are platform.dir/data/fonts. 89 * The Framework resources are platform.dir/data/res. 90 * build.prop is at platform.dir/build.prop. 91 * 92 * 2. test_res.dir: This is the directory for the resources of the test. If not specified, this 93 * falls back to getClass().getProtectionDomain().getCodeSource().getLocation() 94 * 95 * The app resources are at: test_res.dir/testApp/MyApplication/app/src/main/res 96 */ 97public class Main { 98 99 private static final String PLATFORM_DIR_PROPERTY = "platform.dir"; 100 private static final String RESOURCE_DIR_PROPERTY = "test_res.dir"; 101 102 private static final String PLATFORM_DIR; 103 private static final String TEST_RES_DIR; 104 /** Location of the app to test inside {@link #TEST_RES_DIR}*/ 105 private static final String APP_TEST_DIR = "/testApp/MyApplication"; 106 /** Location of the app's res dir inside {@link #TEST_RES_DIR}*/ 107 private static final String APP_TEST_RES = APP_TEST_DIR + "/src/main/res"; 108 109 private static LayoutLog sLayoutLibLog; 110 private static FrameworkResources sFrameworkRepo; 111 private static ResourceRepository sProjectResources; 112 private static ILogger sLogger; 113 private static Bridge sBridge; 114 115 /** List of log messages generated by a render call. It can be used to find specific errors */ 116 private static ArrayList<String> sRenderMessages = Lists.newArrayList(); 117 118 @Rule 119 public static TestWatcher sRenderMessageWatcher = new TestWatcher() { 120 @Override 121 protected void succeeded(Description description) { 122 // We only check error messages if the rest of the test case was successful. 123 if (!sRenderMessages.isEmpty()) { 124 fail(description.getMethodName() + " render error message: " + sRenderMessages.get 125 (0)); 126 } 127 } 128 }; 129 130 static { 131 // Test that System Properties are properly set. 132 PLATFORM_DIR = getPlatformDir(); 133 if (PLATFORM_DIR == null) { 134 fail(String.format("System Property %1$s not properly set. The value is %2$s", 135 PLATFORM_DIR_PROPERTY, System.getProperty(PLATFORM_DIR_PROPERTY))); 136 } 137 138 TEST_RES_DIR = getTestResDir(); 139 if (TEST_RES_DIR == null) { 140 fail(String.format("System property %1$s.dir not properly set. The value is %2$s", 141 RESOURCE_DIR_PROPERTY, System.getProperty(RESOURCE_DIR_PROPERTY))); 142 } 143 } 144 145 private static String getPlatformDir() { 146 String platformDir = System.getProperty(PLATFORM_DIR_PROPERTY); 147 if (platformDir != null && !platformDir.isEmpty() && new File(platformDir).isDirectory()) { 148 return platformDir; 149 } 150 // System Property not set. Try to find the directory in the build directory. 151 String androidHostOut = System.getenv("ANDROID_HOST_OUT"); 152 if (androidHostOut != null) { 153 platformDir = getPlatformDirFromHostOut(new File(androidHostOut)); 154 if (platformDir != null) { 155 return platformDir; 156 } 157 } 158 String workingDirString = System.getProperty("user.dir"); 159 File workingDir = new File(workingDirString); 160 // Test if workingDir is android checkout root. 161 platformDir = getPlatformDirFromRoot(workingDir); 162 if (platformDir != null) { 163 return platformDir; 164 } 165 166 // Test if workingDir is platform/frameworks/base/tools/layoutlib/bridge. 167 File currentDir = workingDir; 168 if (currentDir.getName().equalsIgnoreCase("bridge")) { 169 currentDir = currentDir.getParentFile(); 170 } 171 // Test if currentDir is platform/frameworks/base/tools/layoutlib. That is, root should be 172 // workingDir/../../../../ (4 levels up) 173 for (int i = 0; i < 4; i++) { 174 if (currentDir != null) { 175 currentDir = currentDir.getParentFile(); 176 } 177 } 178 return currentDir == null ? null : getPlatformDirFromRoot(currentDir); 179 } 180 181 private static String getPlatformDirFromRoot(File root) { 182 if (!root.isDirectory()) { 183 return null; 184 } 185 File out = new File(root, "out"); 186 if (!out.isDirectory()) { 187 return null; 188 } 189 File host = new File(out, "host"); 190 if (!host.isDirectory()) { 191 return null; 192 } 193 File[] hosts = host.listFiles(path -> path.isDirectory() && 194 (path.getName().startsWith("linux-") || path.getName().startsWith("darwin-"))); 195 for (File hostOut : hosts) { 196 String platformDir = getPlatformDirFromHostOut(hostOut); 197 if (platformDir != null) { 198 return platformDir; 199 } 200 } 201 return null; 202 } 203 204 private static String getPlatformDirFromHostOut(File out) { 205 if (!out.isDirectory()) { 206 return null; 207 } 208 File sdkDir = new File(out, "sdk"); 209 if (!sdkDir.isDirectory()) { 210 return null; 211 } 212 File[] sdkDirs = sdkDir.listFiles(path -> { 213 // We need to search for $TARGET_PRODUCT (usually, sdk_phone_armv7) 214 return path.isDirectory() && path.getName().startsWith("sdk"); 215 }); 216 for (File dir : sdkDirs) { 217 String platformDir = getPlatformDirFromHostOutSdkSdk(dir); 218 if (platformDir != null) { 219 return platformDir; 220 } 221 } 222 return null; 223 } 224 225 private static String getPlatformDirFromHostOutSdkSdk(File sdkDir) { 226 File[] possibleSdks = sdkDir.listFiles( 227 path -> path.isDirectory() && path.getName().contains("android-sdk")); 228 for (File possibleSdk : possibleSdks) { 229 File platformsDir = new File(possibleSdk, "platforms"); 230 File[] platforms = platformsDir.listFiles( 231 path -> path.isDirectory() && path.getName().startsWith("android-")); 232 if (platforms == null || platforms.length == 0) { 233 continue; 234 } 235 Arrays.sort(platforms, (o1, o2) -> { 236 final int MAX_VALUE = 1000; 237 String suffix1 = o1.getName().substring("android-".length()); 238 String suffix2 = o2.getName().substring("android-".length()); 239 int suff1, suff2; 240 try { 241 suff1 = Integer.parseInt(suffix1); 242 } catch (NumberFormatException e) { 243 suff1 = MAX_VALUE; 244 } 245 try { 246 suff2 = Integer.parseInt(suffix2); 247 } catch (NumberFormatException e) { 248 suff2 = MAX_VALUE; 249 } 250 if (suff1 != MAX_VALUE || suff2 != MAX_VALUE) { 251 return suff2 - suff1; 252 } 253 return suffix2.compareTo(suffix1); 254 }); 255 return platforms[0].getAbsolutePath(); 256 } 257 return null; 258 } 259 260 private static String getTestResDir() { 261 String resourceDir = System.getProperty(RESOURCE_DIR_PROPERTY); 262 if (resourceDir != null && !resourceDir.isEmpty() && new File(resourceDir).isDirectory()) { 263 return resourceDir; 264 } 265 // TEST_RES_DIR not explicitly set. Fallback to the class's source location. 266 try { 267 URL location = Main.class.getProtectionDomain().getCodeSource().getLocation(); 268 return new File(location.getPath()).exists() ? location.getPath() : null; 269 } catch (NullPointerException e) { 270 // Prevent a lot of null checks by just catching the exception. 271 return null; 272 } 273 } 274 275 /** 276 * Initialize the bridge and the resource maps. 277 */ 278 @BeforeClass 279 public static void setUp() { 280 File data_dir = new File(PLATFORM_DIR, "data"); 281 File res = new File(data_dir, "res"); 282 sFrameworkRepo = new FrameworkResources(new FolderWrapper(res)); 283 sFrameworkRepo.loadResources(); 284 sFrameworkRepo.loadPublicResources(getLogger()); 285 286 sProjectResources = 287 new ResourceRepository(new FolderWrapper(TEST_RES_DIR + APP_TEST_RES), false) { 288 @NonNull 289 @Override 290 protected ResourceItem createResourceItem(@NonNull String name) { 291 return new ResourceItem(name); 292 } 293 }; 294 sProjectResources.loadResources(); 295 296 File fontLocation = new File(data_dir, "fonts"); 297 File buildProp = new File(PLATFORM_DIR, "build.prop"); 298 File attrs = new File(res, "values" + File.separator + "attrs.xml"); 299 sBridge = new Bridge(); 300 sBridge.init(ConfigGenerator.loadProperties(buildProp), fontLocation, 301 ConfigGenerator.getEnumMap(attrs), getLayoutLog()); 302 } 303 304 @Before 305 public void beforeTestCase() { 306 sRenderMessages.clear(); 307 } 308 309 /** Test activity.xml */ 310 @Test 311 public void testActivity() throws ClassNotFoundException { 312 renderAndVerify("activity.xml", "activity.png"); 313 } 314 315 /** Test allwidgets.xml */ 316 @Test 317 public void testAllWidgets() throws ClassNotFoundException { 318 renderAndVerify("allwidgets.xml", "allwidgets.png"); 319 320 // We expect fidelity warnings for Path.isConvex. Fail for anything else. 321 sRenderMessages.removeIf(message -> message.equals("Path.isConvex is not supported.")); 322 } 323 324 @Test 325 public void testArrayCheck() throws ClassNotFoundException { 326 renderAndVerify("array_check.xml", "array_check.png"); 327 } 328 329 @Test 330 public void testAllWidgetsTablet() throws ClassNotFoundException { 331 renderAndVerify("allwidgets.xml", "allwidgets_tab.png", ConfigGenerator.NEXUS_7_2012); 332 333 // We expect fidelity warnings for Path.isConvex. Fail for anything else. 334 sRenderMessages.removeIf(message -> message.equals("Path.isConvex is not supported.")); 335 } 336 337 private static void gc() { 338 // See RuntimeUtil#gc in jlibs (http://jlibs.in/) 339 Object obj = new Object(); 340 WeakReference ref = new WeakReference<Object>(obj); 341 obj = null; 342 while(ref.get() != null) { 343 System.gc(); 344 } 345 } 346 347 @AfterClass 348 public static void tearDown() { 349 sLayoutLibLog = null; 350 sFrameworkRepo = null; 351 sProjectResources = null; 352 sLogger = null; 353 sBridge = null; 354 355 gc(); 356 357 System.out.println("Objects still linked from the DelegateManager:"); 358 DelegateManager.dump(System.out); 359 } 360 361 /** Test expand_layout.xml */ 362 @Test 363 public void testExpand() throws ClassNotFoundException { 364 // Create the layout pull parser. 365 LayoutPullParser parser = createLayoutPullParser("expand_vert_layout.xml"); 366 // Create LayoutLibCallback. 367 LayoutLibTestCallback layoutLibCallback = new LayoutLibTestCallback(getLogger()); 368 layoutLibCallback.initResources(); 369 370 ConfigGenerator customConfigGenerator = new ConfigGenerator() 371 .setScreenWidth(300) 372 .setScreenHeight(20) 373 .setDensity(Density.XHIGH) 374 .setNavigation(Navigation.NONAV); 375 376 SessionParams params = getSessionParams(parser, customConfigGenerator, 377 layoutLibCallback, "Theme.Material.Light.NoActionBar.Fullscreen", false, 378 RenderingMode.V_SCROLL, 22); 379 380 renderAndVerify(params, "expand_vert_layout.png"); 381 382 customConfigGenerator = new ConfigGenerator() 383 .setScreenWidth(20) 384 .setScreenHeight(300) 385 .setDensity(Density.XHIGH) 386 .setNavigation(Navigation.NONAV); 387 parser = createLayoutPullParser("expand_horz_layout.xml"); 388 params = getSessionParams(parser, customConfigGenerator, 389 layoutLibCallback, "Theme.Material.Light.NoActionBar.Fullscreen", false, 390 RenderingMode.H_SCROLL, 22); 391 392 renderAndVerify(params, "expand_horz_layout.png"); 393 } 394 395 /** Test indeterminate_progressbar.xml */ 396 @Test 397 public void testVectorAnimation() throws ClassNotFoundException { 398 // Create the layout pull parser. 399 LayoutPullParser parser = createLayoutPullParser("indeterminate_progressbar.xml"); 400 // Create LayoutLibCallback. 401 LayoutLibTestCallback layoutLibCallback = new LayoutLibTestCallback(getLogger()); 402 layoutLibCallback.initResources(); 403 404 SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5, 405 layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false, 406 RenderingMode.V_SCROLL, 22); 407 408 renderAndVerify(params, "animated_vector.png", TimeUnit.SECONDS.toNanos(2)); 409 410 parser = createLayoutPullParser("indeterminate_progressbar.xml"); 411 params = getSessionParams(parser, ConfigGenerator.NEXUS_5, 412 layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false, 413 RenderingMode.V_SCROLL, 22); 414 renderAndVerify(params, "animated_vector_1.png", TimeUnit.SECONDS.toNanos(3)); 415 } 416 417 /** 418 * Test a vector drawable that uses trimStart and trimEnd. It also tests all the primitives 419 * for vector drawables (lines, moves and cubic and quadratic curves). 420 */ 421 @Test 422 public void testVectorDrawable() throws ClassNotFoundException { 423 // Create the layout pull parser. 424 LayoutPullParser parser = createLayoutPullParser("vector_drawable.xml"); 425 // Create LayoutLibCallback. 426 LayoutLibTestCallback layoutLibCallback = new LayoutLibTestCallback(getLogger()); 427 layoutLibCallback.initResources(); 428 429 SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5, 430 layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false, 431 RenderingMode.V_SCROLL, 22); 432 433 renderAndVerify(params, "vector_drawable.png", TimeUnit.SECONDS.toNanos(2)); 434 } 435 436 /** Test activity.xml */ 437 @Test 438 public void testScrolling() throws ClassNotFoundException { 439 // Create the layout pull parser. 440 LayoutPullParser parser = createLayoutPullParser("scrolled.xml"); 441 // Create LayoutLibCallback. 442 LayoutLibTestCallback layoutLibCallback = new LayoutLibTestCallback(getLogger()); 443 layoutLibCallback.initResources(); 444 445 SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5, 446 layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false, 447 RenderingMode.V_SCROLL, 22); 448 params.setForceNoDecor(); 449 params.setExtendedViewInfoMode(true); 450 451 RenderResult result = renderAndVerify(params, "scrolled.png"); 452 assertNotNull(result); 453 assertTrue(result.getResult().isSuccess()); 454 455 ViewInfo rootLayout = result.getRootViews().get(0); 456 // Check the first box in the main LinearLayout 457 assertEquals(-90, rootLayout.getChildren().get(0).getTop()); 458 assertEquals(-30, rootLayout.getChildren().get(0).getLeft()); 459 assertEquals(90, rootLayout.getChildren().get(0).getBottom()); 460 assertEquals(150, rootLayout.getChildren().get(0).getRight()); 461 462 // Check the first box within the nested LinearLayout 463 assertEquals(-450, rootLayout.getChildren().get(5).getChildren().get(0).getTop()); 464 assertEquals(90, rootLayout.getChildren().get(5).getChildren().get(0).getLeft()); 465 assertEquals(-270, rootLayout.getChildren().get(5).getChildren().get(0).getBottom()); 466 assertEquals(690, rootLayout.getChildren().get(5).getChildren().get(0).getRight()); 467 } 468 469 @Test 470 public void testGetResourceNameVariants() throws Exception { 471 // Setup 472 SessionParams params = createSessionParams("", ConfigGenerator.NEXUS_4); 473 AssetManager assetManager = AssetManager.getSystem(); 474 DisplayMetrics metrics = new DisplayMetrics(); 475 Configuration configuration = RenderAction.getConfiguration(params); 476 Resources resources = new Resources(assetManager, metrics, configuration); 477 resources.mLayoutlibCallback = params.getLayoutlibCallback(); 478 resources.mContext = 479 new BridgeContext(params.getProjectKey(), metrics, params.getResources(), 480 params.getAssets(), params.getLayoutlibCallback(), configuration, 481 params.getTargetSdkVersion(), params.isRtlSupported()); 482 // Test 483 assertEquals("android:style/ButtonBar", 484 resources.getResourceName(android.R.style.ButtonBar)); 485 assertEquals("android", resources.getResourcePackageName(android.R.style.ButtonBar)); 486 assertEquals("ButtonBar", resources.getResourceEntryName(android.R.style.ButtonBar)); 487 assertEquals("style", resources.getResourceTypeName(android.R.style.ButtonBar)); 488 int id = resources.mLayoutlibCallback.getResourceId(ResourceType.STRING, "app_name"); 489 assertEquals("com.android.layoutlib.test.myapplication:string/app_name", 490 resources.getResourceName(id)); 491 assertEquals("com.android.layoutlib.test.myapplication", 492 resources.getResourcePackageName(id)); 493 assertEquals("string", resources.getResourceTypeName(id)); 494 assertEquals("app_name", resources.getResourceEntryName(id)); 495 } 496 497 @NonNull 498 private LayoutPullParser createLayoutPullParser(String layoutPath) { 499 return new LayoutPullParser(APP_TEST_RES + "/layout/" + layoutPath); 500 } 501 502 /** 503 * Create a new rendering session and test that rendering the given layout doesn't throw any 504 * exceptions and matches the provided image. 505 * <p> 506 * If frameTimeNanos is >= 0 a frame will be executed during the rendering. The time indicates 507 * how far in the future is. 508 */ 509 @Nullable 510 private RenderResult renderAndVerify(SessionParams params, String goldenFileName, long frameTimeNanos) 511 throws ClassNotFoundException { 512 // TODO: Set up action bar handler properly to test menu rendering. 513 // Create session params. 514 RenderSession session = sBridge.createSession(params); 515 516 if (frameTimeNanos != -1) { 517 session.setElapsedFrameTimeNanos(frameTimeNanos); 518 } 519 520 if (!session.getResult().isSuccess()) { 521 getLogger().error(session.getResult().getException(), 522 session.getResult().getErrorMessage()); 523 } 524 // Render the session with a timeout of 50s. 525 Result renderResult = session.render(50000); 526 if (!renderResult.isSuccess()) { 527 getLogger().error(session.getResult().getException(), 528 session.getResult().getErrorMessage()); 529 } 530 try { 531 String goldenImagePath = APP_TEST_DIR + "/golden/" + goldenFileName; 532 ImageUtils.requireSimilar(goldenImagePath, session.getImage()); 533 534 return RenderResult.getFromSession(session); 535 } catch (IOException e) { 536 getLogger().error(e, e.getMessage()); 537 } finally { 538 session.dispose(); 539 } 540 541 return null; 542 } 543 544 /** 545 * Create a new rendering session and test that rendering the given layout doesn't throw any 546 * exceptions and matches the provided image. 547 */ 548 @Nullable 549 private RenderResult renderAndVerify(SessionParams params, String goldenFileName) 550 throws ClassNotFoundException { 551 return renderAndVerify(params, goldenFileName, -1); 552 } 553 554 /** 555 * Create a new rendering session and test that rendering the given layout on nexus 5 556 * doesn't throw any exceptions and matches the provided image. 557 */ 558 @Nullable 559 private RenderResult renderAndVerify(String layoutFileName, String goldenFileName) 560 throws ClassNotFoundException { 561 return renderAndVerify(layoutFileName, goldenFileName, ConfigGenerator.NEXUS_5); 562 } 563 564 /** 565 * Create a new rendering session and test that rendering the given layout on given device 566 * doesn't throw any exceptions and matches the provided image. 567 */ 568 @Nullable 569 private RenderResult renderAndVerify(String layoutFileName, String goldenFileName, 570 ConfigGenerator deviceConfig) 571 throws ClassNotFoundException { 572 SessionParams params = createSessionParams(layoutFileName, deviceConfig); 573 return renderAndVerify(params, goldenFileName); 574 } 575 576 private SessionParams createSessionParams(String layoutFileName, ConfigGenerator deviceConfig) 577 throws ClassNotFoundException { 578 // Create the layout pull parser. 579 LayoutPullParser parser = createLayoutPullParser(layoutFileName); 580 // Create LayoutLibCallback. 581 LayoutLibTestCallback layoutLibCallback = new LayoutLibTestCallback(getLogger()); 582 layoutLibCallback.initResources(); 583 // TODO: Set up action bar handler properly to test menu rendering. 584 // Create session params. 585 return getSessionParams(parser, deviceConfig, 586 layoutLibCallback, "AppTheme", true, RenderingMode.NORMAL, 22); 587 } 588 589 /** 590 * Uses Theme.Material and Target sdk version as 22. 591 */ 592 private SessionParams getSessionParams(LayoutPullParser layoutParser, 593 ConfigGenerator configGenerator, LayoutLibTestCallback layoutLibCallback, 594 String themeName, boolean isProjectTheme, RenderingMode renderingMode, int targetSdk) { 595 FolderConfiguration config = configGenerator.getFolderConfig(); 596 ResourceResolver resourceResolver = 597 ResourceResolver.create(sProjectResources.getConfiguredResources(config), 598 sFrameworkRepo.getConfiguredResources(config), 599 themeName, isProjectTheme); 600 601 SessionParams sessionParams = new SessionParams( 602 layoutParser, 603 renderingMode, 604 null /*used for caching*/, 605 configGenerator.getHardwareConfig(), 606 resourceResolver, 607 layoutLibCallback, 608 0, 609 targetSdk, 610 getLayoutLog()); 611 sessionParams.setFlag(RenderParamsFlags.FLAG_DO_NOT_RENDER_ON_CREATE, true); 612 return sessionParams; 613 } 614 615 private static LayoutLog getLayoutLog() { 616 if (sLayoutLibLog == null) { 617 sLayoutLibLog = new LayoutLog() { 618 @Override 619 public void warning(String tag, String message, Object data) { 620 System.out.println("Warning " + tag + ": " + message); 621 failWithMsg(message); 622 } 623 624 @Override 625 public void fidelityWarning(@Nullable String tag, String message, 626 Throwable throwable, Object data) { 627 628 System.out.println("FidelityWarning " + tag + ": " + message); 629 if (throwable != null) { 630 throwable.printStackTrace(); 631 } 632 failWithMsg(message == null ? "" : message); 633 } 634 635 @Override 636 public void error(String tag, String message, Object data) { 637 System.out.println("Error " + tag + ": " + message); 638 failWithMsg(message); 639 } 640 641 @Override 642 public void error(String tag, String message, Throwable throwable, Object data) { 643 System.out.println("Error " + tag + ": " + message); 644 if (throwable != null) { 645 throwable.printStackTrace(); 646 } 647 failWithMsg(message); 648 } 649 }; 650 } 651 return sLayoutLibLog; 652 } 653 654 private static ILogger getLogger() { 655 if (sLogger == null) { 656 sLogger = new ILogger() { 657 @Override 658 public void error(Throwable t, @Nullable String msgFormat, Object... args) { 659 if (t != null) { 660 t.printStackTrace(); 661 } 662 failWithMsg(msgFormat == null ? "" : msgFormat, args); 663 } 664 665 @Override 666 public void warning(@NonNull String msgFormat, Object... args) { 667 failWithMsg(msgFormat, args); 668 } 669 670 @Override 671 public void info(@NonNull String msgFormat, Object... args) { 672 // pass. 673 } 674 675 @Override 676 public void verbose(@NonNull String msgFormat, Object... args) { 677 // pass. 678 } 679 }; 680 } 681 return sLogger; 682 } 683 684 private static void failWithMsg(@NonNull String msgFormat, Object... args) { 685 sRenderMessages.add(args == null ? msgFormat : String.format(msgFormat, args)); 686 } 687} 688