OSDN Git Service

LayoutLib: Change nullity annotations. [DO NOT MERGE]
[android-x86/frameworks-base.git] / tools / layoutlib / bridge / tests / src / com / android / layoutlib / bridge / intensive / Main.java
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
17 package com.android.layoutlib.bridge.intensive;
18
19 import com.android.ide.common.rendering.api.LayoutLog;
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.FrameworkResources;
25 import com.android.ide.common.resources.ResourceItem;
26 import com.android.ide.common.resources.ResourceRepository;
27 import com.android.ide.common.resources.ResourceResolver;
28 import com.android.ide.common.resources.configuration.FolderConfiguration;
29 import com.android.io.FolderWrapper;
30 import com.android.layoutlib.bridge.Bridge;
31 import com.android.layoutlib.bridge.intensive.setup.ConfigGenerator;
32 import com.android.layoutlib.bridge.intensive.setup.LayoutLibTestCallback;
33 import com.android.layoutlib.bridge.intensive.setup.LayoutPullParser;
34 import com.android.resources.Density;
35 import com.android.resources.Navigation;
36 import com.android.utils.ILogger;
37
38 import org.junit.Before;
39 import org.junit.Test;
40
41 import android.annotation.NonNull;
42
43 import java.io.File;
44 import java.io.FileFilter;
45 import java.io.IOException;
46 import java.net.URL;
47 import java.util.Arrays;
48 import java.util.Comparator;
49
50 import static org.junit.Assert.fail;
51
52 /**
53  * This is a set of tests that loads all the framework resources and a project checked in this
54  * test's resources. The main dependencies
55  * are:
56  * 1. Fonts directory.
57  * 2. Framework Resources.
58  * 3. App resources.
59  * 4. build.prop file
60  *
61  * These are configured by two variables set in the system properties.
62  *
63  * 1. platform.dir: This is the directory for the current platform in the built SDK
64  *     (.../sdk/platforms/android-<version>).
65  *
66  *     The fonts are platform.dir/data/fonts.
67  *     The Framework resources are platform.dir/data/res.
68  *     build.prop is at platform.dir/build.prop.
69  *
70  * 2. test_res.dir: This is the directory for the resources of the test. If not specified, this
71  *     falls back to getClass().getProtectionDomain().getCodeSource().getLocation()
72  *
73  *     The app resources are at: test_res.dir/testApp/MyApplication/app/src/main/res
74  */
75 public class Main {
76
77     private static final String PLATFORM_DIR_PROPERTY = "platform.dir";
78     private static final String RESOURCE_DIR_PROPERTY = "test_res.dir";
79
80     private static final String PLATFORM_DIR;
81     private static final String TEST_RES_DIR;
82     /** Location of the app to test inside {@link #TEST_RES_DIR}*/
83     private static final String APP_TEST_DIR = "/testApp/MyApplication";
84     /** Location of the app's res dir inside {@link #TEST_RES_DIR}*/
85     private static final String APP_TEST_RES = APP_TEST_DIR + "/src/main/res";
86
87     private LayoutLog mLayoutLibLog;
88     private FrameworkResources mFrameworkRepo;
89     private ResourceRepository mProjectResources;
90     private ILogger mLogger;
91     private Bridge mBridge;
92
93     static {
94         // Test that System Properties are properly set.
95         PLATFORM_DIR = getPlatformDir();
96         if (PLATFORM_DIR == null) {
97             fail(String.format("System Property %1$s not properly set. The value is %2$s",
98                     PLATFORM_DIR_PROPERTY, System.getProperty(PLATFORM_DIR_PROPERTY)));
99         }
100
101         TEST_RES_DIR = getTestResDir();
102         if (TEST_RES_DIR == null) {
103             fail(String.format("System property %1$s.dir not properly set. The value is %2$s",
104                     RESOURCE_DIR_PROPERTY, System.getProperty(RESOURCE_DIR_PROPERTY)));
105         }
106     }
107
108     private static String getPlatformDir() {
109         String platformDir = System.getProperty(PLATFORM_DIR_PROPERTY);
110         if (platformDir != null && !platformDir.isEmpty() && new File(platformDir).isDirectory()) {
111             return platformDir;
112         }
113         // System Property not set. Try to find the directory in the build directory.
114         String androidHostOut = System.getenv("ANDROID_HOST_OUT");
115         if (androidHostOut != null) {
116             platformDir = getPlatformDirFromHostOut(new File(androidHostOut));
117             if (platformDir != null) {
118                 return platformDir;
119             }
120         }
121         String workingDirString = System.getProperty("user.dir");
122         File workingDir = new File(workingDirString);
123         // Test if workingDir is android checkout root.
124         platformDir = getPlatformDirFromRoot(workingDir);
125         if (platformDir != null) {
126             return platformDir;
127         }
128
129         // Test if workingDir is platform/frameworks/base/tools/layoutlib/bridge.
130         File currentDir = workingDir;
131         if (currentDir.getName().equalsIgnoreCase("bridge")) {
132             currentDir = currentDir.getParentFile();
133         }
134         // Test if currentDir is  platform/frameworks/base/tools/layoutlib. That is, root should be
135         // workingDir/../../../../  (4 levels up)
136         for (int i = 0; i < 4; i++) {
137             if (currentDir != null) {
138                 currentDir = currentDir.getParentFile();
139             }
140         }
141         return currentDir == null ? null : getPlatformDirFromRoot(currentDir);
142     }
143
144     private static String getPlatformDirFromRoot(File root) {
145         if (!root.isDirectory()) {
146             return null;
147         }
148         File out = new File(root, "out");
149         if (!out.isDirectory()) {
150             return null;
151         }
152         File host = new File(out, "host");
153         if (!host.isDirectory()) {
154             return null;
155         }
156         File[] hosts = host.listFiles(new FileFilter() {
157             @Override
158             public boolean accept(File path) {
159                 return path.isDirectory() && (path.getName().startsWith("linux-") || path.getName()
160                         .startsWith("darwin-"));
161             }
162         });
163         for (File hostOut : hosts) {
164             String platformDir = getPlatformDirFromHostOut(hostOut);
165             if (platformDir != null) {
166                 return platformDir;
167             }
168         }
169         return null;
170     }
171
172     private static String getPlatformDirFromHostOut(File out) {
173         if (!out.isDirectory()) {
174             return null;
175         }
176         File sdkDir = new File(out, "sdk");
177         if (!sdkDir.isDirectory()) {
178             return null;
179         }
180         File[] sdkDirs = sdkDir.listFiles(new FileFilter() {
181             @Override
182             public boolean accept(File path) {
183                 // We need to search for $TARGET_PRODUCT (usually, sdk_phone_armv7)
184                 return path.isDirectory() && path.getName().startsWith("sdk");
185             }
186         });
187         for (File dir : sdkDirs) {
188             String platformDir = getPlatformDirFromHostOutSdkSdk(dir);
189             if (platformDir != null) {
190                 return platformDir;
191             }
192         }
193         return null;
194     }
195
196     private static String getPlatformDirFromHostOutSdkSdk(File sdkDir) {
197         File[] possibleSdks = sdkDir.listFiles(new FileFilter() {
198             @Override
199             public boolean accept(File path) {
200                 return path.isDirectory() && path.getName().contains("android-sdk");
201             }
202         });
203         for (File possibleSdk : possibleSdks) {
204             File platformsDir = new File(possibleSdk, "platforms");
205             File[] platforms = platformsDir.listFiles(new FileFilter() {
206                 @Override
207                 public boolean accept(File path) {
208                     return path.isDirectory() && path.getName().startsWith("android-");
209                 }
210             });
211             if (platforms == null || platforms.length == 0) {
212                 continue;
213             }
214             Arrays.sort(platforms, new Comparator<File>() {
215                 // Codenames before ints. Higher APIs precede lower.
216                 @Override
217                 public int compare(File o1, File o2) {
218                     final int MAX_VALUE = 1000;
219                     String suffix1 = o1.getName().substring("android-".length());
220                     String suffix2 = o2.getName().substring("android-".length());
221                     int suff1, suff2;
222                     try {
223                         suff1 = Integer.parseInt(suffix1);
224                     } catch (NumberFormatException e) {
225                         suff1 = MAX_VALUE;
226                     }
227                     try {
228                         suff2 = Integer.parseInt(suffix2);
229                     } catch (NumberFormatException e) {
230                         suff2 = MAX_VALUE;
231                     }
232                     if (suff1 != MAX_VALUE || suff2 != MAX_VALUE) {
233                         return suff2 - suff1;
234                     }
235                     return suffix2.compareTo(suffix1);
236                 }
237             });
238             return platforms[0].getAbsolutePath();
239         }
240         return null;
241     }
242
243     private static String getTestResDir() {
244         String resourceDir = System.getProperty(RESOURCE_DIR_PROPERTY);
245         if (resourceDir != null && !resourceDir.isEmpty() && new File(resourceDir).isDirectory()) {
246             return resourceDir;
247         }
248         // TEST_RES_DIR not explicitly set. Fallback to the class's source location.
249         try {
250             URL location = Main.class.getProtectionDomain().getCodeSource().getLocation();
251             return new File(location.getPath()).exists() ? location.getPath() : null;
252         } catch (NullPointerException e) {
253             // Prevent a lot of null checks by just catching the exception.
254             return null;
255         }
256     }
257     /**
258      * Initialize the bridge and the resource maps.
259      */
260     @Before
261     public void setUp() {
262         File data_dir = new File(PLATFORM_DIR, "data");
263         File res = new File(data_dir, "res");
264         mFrameworkRepo = new FrameworkResources(new FolderWrapper(res));
265         mFrameworkRepo.loadResources();
266         mFrameworkRepo.loadPublicResources(getLogger());
267
268         mProjectResources =
269                 new ResourceRepository(new FolderWrapper(TEST_RES_DIR + APP_TEST_RES), false) {
270             @NonNull
271             @Override
272             protected ResourceItem createResourceItem(@NonNull String name) {
273                 return new ResourceItem(name);
274             }
275         };
276         mProjectResources.loadResources();
277
278         File fontLocation = new File(data_dir, "fonts");
279         File buildProp = new File(PLATFORM_DIR, "build.prop");
280         File attrs = new File(res, "values" + File.separator + "attrs.xml");
281         mBridge = new Bridge();
282         mBridge.init(ConfigGenerator.loadProperties(buildProp), fontLocation,
283                 ConfigGenerator.getEnumMap(attrs), getLayoutLog());
284     }
285
286     /** Text activity.xml */
287     @Test
288     public void testActivity() throws ClassNotFoundException {
289         renderAndVerify("activity.xml", "activity.png");
290
291     }
292
293     /** Test allwidgets.xml */
294     @Test
295     public void testAllWidgets() throws ClassNotFoundException {
296         renderAndVerify("allwidgets.xml", "allwidgets.png");
297     }
298
299     /** Test expand_layout.xml */
300     @Test
301     public void testExpand() throws ClassNotFoundException {
302         // Create the layout pull parser.
303         LayoutPullParser parser = new LayoutPullParser(APP_TEST_RES + "/layout/" +
304                 "expand_vert_layout.xml");
305         // Create LayoutLibCallback.
306         LayoutLibTestCallback layoutLibCallback = new LayoutLibTestCallback(getLogger());
307         layoutLibCallback.initResources();
308
309         ConfigGenerator customConfigGenerator = new ConfigGenerator()
310                 .setScreenWidth(300)
311                 .setScreenHeight(20)
312                 .setDensity(Density.XHIGH)
313                 .setNavigation(Navigation.NONAV);
314
315         SessionParams params = getSessionParams(parser, customConfigGenerator,
316                 layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", RenderingMode.V_SCROLL,
317                 22);
318
319         renderAndVerify(params, "expand_vert_layout.png");
320
321         customConfigGenerator = new ConfigGenerator()
322                 .setScreenWidth(20)
323                 .setScreenHeight(300)
324                 .setDensity(Density.XHIGH)
325                 .setNavigation(Navigation.NONAV);
326         parser = new LayoutPullParser(APP_TEST_RES + "/layout/" +
327                 "expand_horz_layout.xml");
328         params = getSessionParams(parser, customConfigGenerator,
329                 layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", RenderingMode
330                         .H_SCROLL, 22);
331
332         renderAndVerify(params, "expand_horz_layout.png");
333     }
334
335     /**
336      * Create a new rendering session and test that rendering given layout on nexus 5
337      * doesn't throw any exceptions and matches the provided image.
338      */
339     private void renderAndVerify(SessionParams params, String goldenFileName)
340             throws ClassNotFoundException {
341         // TODO: Set up action bar handler properly to test menu rendering.
342         // Create session params.
343         RenderSession session = mBridge.createSession(params);
344         if (!session.getResult().isSuccess()) {
345             getLogger().error(session.getResult().getException(),
346                     session.getResult().getErrorMessage());
347         }
348         // Render the session with a timeout of 50s.
349         Result renderResult = session.render(50000);
350         if (!renderResult.isSuccess()) {
351             getLogger().error(session.getResult().getException(),
352                     session.getResult().getErrorMessage());
353         }
354         try {
355             String goldenImagePath = APP_TEST_DIR + "/golden/" + goldenFileName;
356             ImageUtils.requireSimilar(goldenImagePath, session.getImage());
357         } catch (IOException e) {
358             getLogger().error(e, e.getMessage());
359         }
360     }
361
362     /**
363      * Create a new rendering session and test that rendering given layout on nexus 5
364      * doesn't throw any exceptions and matches the provided image.
365      */
366     private void renderAndVerify(String layoutFileName, String goldenFileName)
367             throws ClassNotFoundException {
368         // Create the layout pull parser.
369         LayoutPullParser parser = new LayoutPullParser(APP_TEST_RES + "/layout/" + layoutFileName);
370         // Create LayoutLibCallback.
371         LayoutLibTestCallback layoutLibCallback = new LayoutLibTestCallback(getLogger());
372         layoutLibCallback.initResources();
373         // TODO: Set up action bar handler properly to test menu rendering.
374         // Create session params.
375         SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
376                 layoutLibCallback, "Theme.Material.Light.DarkActionBar", RenderingMode.NORMAL, 22);
377         renderAndVerify(params, goldenFileName);
378     }
379
380     /**
381      * Uses Theme.Material and Target sdk version as 22.
382      */
383     private SessionParams getSessionParams(LayoutPullParser layoutParser,
384             ConfigGenerator configGenerator, LayoutLibTestCallback layoutLibCallback,
385             String themeName, RenderingMode renderingMode, int targetSdk) {
386         FolderConfiguration config = configGenerator.getFolderConfig();
387         ResourceResolver resourceResolver =
388                 ResourceResolver.create(mProjectResources.getConfiguredResources(config),
389                         mFrameworkRepo.getConfiguredResources(config),
390                         themeName, false);
391
392         return new SessionParams(
393                 layoutParser,
394                 renderingMode,
395                 null /*used for caching*/,
396                 configGenerator.getHardwareConfig(),
397                 resourceResolver,
398                 layoutLibCallback,
399                 0,
400                 targetSdk,
401                 getLayoutLog());
402     }
403
404     private LayoutLog getLayoutLog() {
405         if (mLayoutLibLog == null) {
406             mLayoutLibLog = new LayoutLog() {
407                 @Override
408                 public void warning(String tag, String message, Object data) {
409                     System.out.println("Warning " + tag + ": " + message);
410                     failWithMsg(message);
411                 }
412
413                 @Override
414                 public void fidelityWarning(String tag, String message, Throwable throwable,
415                         Object data) {
416                     System.out.println("FidelityWarning " + tag + ": " + message);
417                     if (throwable != null) {
418                         throwable.printStackTrace();
419                     }
420                     failWithMsg(message);
421                 }
422
423                 @Override
424                 public void error(String tag, String message, Object data) {
425                     System.out.println("Error " + tag + ": " + message);
426                     failWithMsg(message);
427                 }
428
429                 @Override
430                 public void error(String tag, String message, Throwable throwable, Object data) {
431                     System.out.println("Error " + tag + ": " + message);
432                     if (throwable != null) {
433                         throwable.printStackTrace();
434                     }
435                     failWithMsg(message);
436                 }
437             };
438         }
439         return mLayoutLibLog;
440     }
441
442     private ILogger getLogger() {
443         if (mLogger == null) {
444             mLogger = new ILogger() {
445                 @Override
446                 public void error(Throwable t, String msgFormat, Object... args) {
447                     if (t != null) {
448                         t.printStackTrace();
449                     }
450                     failWithMsg(msgFormat, args);
451                 }
452
453                 @Override
454                 public void warning(@NonNull String msgFormat, Object... args) {
455                     failWithMsg(msgFormat, args);
456                 }
457
458                 @Override
459                 public void info(@NonNull String msgFormat, Object... args) {
460                     // pass.
461                 }
462
463                 @Override
464                 public void verbose(@NonNull String msgFormat, Object... args) {
465                     // pass.
466                 }
467             };
468         }
469         return mLogger;
470     }
471
472     private static void failWithMsg(@NonNull String msgFormat, Object... args) {
473         fail(args == null ? "" : String.format(msgFormat, args));
474     }
475 }