• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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