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