blob: d0c04d7e3de48a0f57f00cdf84f277fd9029ad01 [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.layoutlib.bridge.intensive;
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.ide.common.rendering.api.RenderSession;
import com.android.ide.common.rendering.api.Result;
import com.android.ide.common.rendering.api.SessionParams;
import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
import com.android.ide.common.rendering.api.ViewInfo;
import com.android.ide.common.resources.FrameworkResources;
import com.android.ide.common.resources.ResourceItem;
import com.android.ide.common.resources.ResourceRepository;
import com.android.ide.common.resources.ResourceResolver;
import com.android.ide.common.resources.configuration.FolderConfiguration;
import com.android.io.FolderWrapper;
import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.android.RenderParamsFlags;
import com.android.layoutlib.bridge.impl.DelegateManager;
import com.android.layoutlib.bridge.impl.RenderAction;
import com.android.layoutlib.bridge.intensive.setup.ConfigGenerator;
import com.android.layoutlib.bridge.intensive.setup.LayoutLibTestCallback;
import com.android.layoutlib.bridge.intensive.setup.LayoutPullParser;
import com.android.resources.Density;
import com.android.resources.Navigation;
import com.android.resources.ResourceType;
import com.android.resources.ScreenOrientation;
import com.android.tools.layoutlib.java.System_Delegate;
import com.android.utils.ILogger;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.AssetManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.util.DisplayMetrics;
import java.io.File;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import com.google.android.collect.Lists;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* This is a set of tests that loads all the framework resources and a project checked in this
* test's resources. The main dependencies
* are:
* 1. Fonts directory.
* 2. Framework Resources.
* 3. App resources.
* 4. build.prop file
*
* These are configured by two variables set in the system properties.
*
* 1. platform.dir: This is the directory for the current platform in the built SDK
* (.../sdk/platforms/android-<version>).
*
* The fonts are platform.dir/data/fonts.
* The Framework resources are platform.dir/data/res.
* build.prop is at platform.dir/build.prop.
*
* 2. test_res.dir: This is the directory for the resources of the test. If not specified, this
* falls back to getClass().getProtectionDomain().getCodeSource().getLocation()
*
* The app resources are at: test_res.dir/testApp/MyApplication/app/src/main/res
*/
public class Main {
private static final String PLATFORM_DIR_PROPERTY = "platform.dir";
private static final String RESOURCE_DIR_PROPERTY = "test_res.dir";
private static final String PLATFORM_DIR;
private static final String TEST_RES_DIR;
/** Location of the app to test inside {@link #TEST_RES_DIR}*/
private static final String APP_TEST_DIR = "testApp/MyApplication";
/** Location of the app's res dir inside {@link #TEST_RES_DIR}*/
private static final String APP_TEST_RES = APP_TEST_DIR + "/src/main/res";
private static final String APP_CLASSES_LOCATION =
APP_TEST_DIR + "/build/intermediates/classes/debug/";
private static LayoutLog sLayoutLibLog;
private static FrameworkResources sFrameworkRepo;
private static ResourceRepository sProjectResources;
private static ILogger sLogger;
private static Bridge sBridge;
/** List of log messages generated by a render call. It can be used to find specific errors */
private static ArrayList<String> sRenderMessages = Lists.newArrayList();
// Default class loader with access to the app classes
private ClassLoader mDefaultClassLoader =
new ModuleClassLoader(APP_CLASSES_LOCATION, getClass().getClassLoader());
@Rule
public TestWatcher sRenderMessageWatcher = new TestWatcher() {
@Override
protected void succeeded(Description description) {
// We only check error messages if the rest of the test case was successful.
if (!sRenderMessages.isEmpty()) {
fail(description.getMethodName() + " render error message: " + sRenderMessages.get
(0));
}
}
};
static {
// Test that System Properties are properly set.
PLATFORM_DIR = getPlatformDir();
if (PLATFORM_DIR == null) {
fail(String.format("System Property %1$s not properly set. The value is %2$s",
PLATFORM_DIR_PROPERTY, System.getProperty(PLATFORM_DIR_PROPERTY)));
}
TEST_RES_DIR = getTestResDir();
if (TEST_RES_DIR == null) {
fail(String.format("System property %1$s.dir not properly set. The value is %2$s",
RESOURCE_DIR_PROPERTY, System.getProperty(RESOURCE_DIR_PROPERTY)));
}
}
private static String getPlatformDir() {
String platformDir = System.getProperty(PLATFORM_DIR_PROPERTY);
if (platformDir != null && !platformDir.isEmpty() && new File(platformDir).isDirectory()) {
return platformDir;
}
// System Property not set. Try to find the directory in the build directory.
String androidHostOut = System.getenv("ANDROID_HOST_OUT");
if (androidHostOut != null) {
platformDir = getPlatformDirFromHostOut(new File(androidHostOut));
if (platformDir != null) {
return platformDir;
}
}
String workingDirString = System.getProperty("user.dir");
File workingDir = new File(workingDirString);
// Test if workingDir is android checkout root.
platformDir = getPlatformDirFromRoot(workingDir);
if (platformDir != null) {
return platformDir;
}
// Test if workingDir is platform/frameworks/base/tools/layoutlib/bridge.
File currentDir = workingDir;
if (currentDir.getName().equalsIgnoreCase("bridge")) {
currentDir = currentDir.getParentFile();
}
// Test if currentDir is platform/frameworks/base/tools/layoutlib. That is, root should be
// workingDir/../../../../ (4 levels up)
for (int i = 0; i < 4; i++) {
if (currentDir != null) {
currentDir = currentDir.getParentFile();
}
}
return currentDir == null ? null : getPlatformDirFromRoot(currentDir);
}
private static String getPlatformDirFromRoot(File root) {
if (!root.isDirectory()) {
return null;
}
File out = new File(root, "out");
if (!out.isDirectory()) {
return null;
}
File host = new File(out, "host");
if (!host.isDirectory()) {
return null;
}
File[] hosts = host.listFiles(path -> path.isDirectory() &&
(path.getName().startsWith("linux-") || path.getName().startsWith("darwin-")));
assert hosts != null;
for (File hostOut : hosts) {
String platformDir = getPlatformDirFromHostOut(hostOut);
if (platformDir != null) {
return platformDir;
}
}
return null;
}
private static String getPlatformDirFromHostOut(File out) {
if (!out.isDirectory()) {
return null;
}
File sdkDir = new File(out, "sdk");
if (!sdkDir.isDirectory()) {
return null;
}
File[] sdkDirs = sdkDir.listFiles(path -> {
// We need to search for $TARGET_PRODUCT (usually, sdk_phone_armv7)
return path.isDirectory() && path.getName().startsWith("sdk");
});
assert sdkDirs != null;
for (File dir : sdkDirs) {
String platformDir = getPlatformDirFromHostOutSdkSdk(dir);
if (platformDir != null) {
return platformDir;
}
}
return null;
}
private static String getPlatformDirFromHostOutSdkSdk(File sdkDir) {
File[] possibleSdks = sdkDir.listFiles(
path -> path.isDirectory() && path.getName().contains("android-sdk"));
assert possibleSdks != null;
for (File possibleSdk : possibleSdks) {
File platformsDir = new File(possibleSdk, "platforms");
File[] platforms = platformsDir.listFiles(
path -> path.isDirectory() && path.getName().startsWith("android-"));
if (platforms == null || platforms.length == 0) {
continue;
}
Arrays.sort(platforms, (o1, o2) -> {
final int MAX_VALUE = 1000;
String suffix1 = o1.getName().substring("android-".length());
String suffix2 = o2.getName().substring("android-".length());
int suff1, suff2;
try {
suff1 = Integer.parseInt(suffix1);
} catch (NumberFormatException e) {
suff1 = MAX_VALUE;
}
try {
suff2 = Integer.parseInt(suffix2);
} catch (NumberFormatException e) {
suff2 = MAX_VALUE;
}
if (suff1 != MAX_VALUE || suff2 != MAX_VALUE) {
return suff2 - suff1;
}
return suffix2.compareTo(suffix1);
});
return platforms[0].getAbsolutePath();
}
return null;
}
private static String getTestResDir() {
String resourceDir = System.getProperty(RESOURCE_DIR_PROPERTY);
if (resourceDir != null && !resourceDir.isEmpty() && new File(resourceDir).isDirectory()) {
return resourceDir;
}
// TEST_RES_DIR not explicitly set. Fallback to the class's source location.
try {
URL location = Main.class.getProtectionDomain().getCodeSource().getLocation();
return new File(location.getPath()).exists() ? location.getPath() : null;
} catch (NullPointerException e) {
// Prevent a lot of null checks by just catching the exception.
return null;
}
}
/**
* Initialize the bridge and the resource maps.
*/
@BeforeClass
public static void setUp() {
File data_dir = new File(PLATFORM_DIR, "data");
File res = new File(data_dir, "res");
sFrameworkRepo = new FrameworkResources(new FolderWrapper(res));
sFrameworkRepo.loadResources();
sFrameworkRepo.loadPublicResources(getLogger());
sProjectResources =
new ResourceRepository(new FolderWrapper(TEST_RES_DIR + "/" + APP_TEST_RES),
false) {
@NonNull
@Override
protected ResourceItem createResourceItem(@NonNull String name) {
return new ResourceItem(name);
}
};
sProjectResources.loadResources();
File fontLocation = new File(data_dir, "fonts");
File buildProp = new File(PLATFORM_DIR, "build.prop");
File attrs = new File(res, "values" + File.separator + "attrs.xml");
sBridge = new Bridge();
sBridge.init(ConfigGenerator.loadProperties(buildProp), fontLocation,
ConfigGenerator.getEnumMap(attrs), getLayoutLog());
Bridge.getLock().lock();
try {
Bridge.setLog(getLayoutLog());
} finally {
Bridge.getLock().unlock();
}
}
@Before
public void beforeTestCase() {
sRenderMessages.clear();
}
/** Test activity.xml */
@Test
public void testActivity() throws ClassNotFoundException {
renderAndVerify("activity.xml", "activity.png");
}
@Test
public void testActivityOnOldTheme() throws ClassNotFoundException {
LayoutLibTestCallback layoutLibCallback =
new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
layoutLibCallback.initResources();
LayoutPullParser parser = createLayoutPullParser("simple_activity.xml");
SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
layoutLibCallback, "Theme.NoTitleBar", false,
RenderingMode.NORMAL, 22);
renderAndVerify(params, "simple_activity-old-theme.png");
}
@Test
public void testTranslucentBars() throws ClassNotFoundException {
LayoutLibTestCallback layoutLibCallback =
new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
layoutLibCallback.initResources();
LayoutPullParser parser = createLayoutPullParser("four_corners.xml");
SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
layoutLibCallback, "Theme.Material.Light.NoActionBar.TranslucentDecor", false,
RenderingMode.NORMAL, 22);
renderAndVerify(params, "four_corners_translucent.png");
parser = createLayoutPullParser("four_corners.xml");
params = getSessionParams(parser, ConfigGenerator.NEXUS_5_LAND,
layoutLibCallback, "Theme.Material.Light.NoActionBar.TranslucentDecor", false,
RenderingMode.NORMAL, 22);
renderAndVerify(params, "four_corners_translucent_land.png");
parser = createLayoutPullParser("four_corners.xml");
params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
layoutLibCallback, "Theme.Material.Light.NoActionBar", false,
RenderingMode.NORMAL, 22);
renderAndVerify(params, "four_corners.png");
}
private static void gc() {
// See RuntimeUtil#gc in jlibs (http://jlibs.in/)
Object obj = new Object();
WeakReference ref = new WeakReference<>(obj);
//noinspection UnusedAssignment
obj = null;
while(ref.get() != null) {
System.gc();
System.runFinalization();
}
System.gc();
System.runFinalization();
}
/** Test allwidgets.xml */
@Test
public void testAllWidgets() throws ClassNotFoundException {
renderAndVerify("allwidgets.xml", "allwidgets.png");
// We expect fidelity warnings for Path.isConvex. Fail for anything else.
sRenderMessages.removeIf(message -> message.equals("Path.isConvex is not supported."));
}
@Test
public void testArrayCheck() throws ClassNotFoundException {
renderAndVerify("array_check.xml", "array_check.png");
}
@Test
public void testAllWidgetsTablet() throws ClassNotFoundException {
renderAndVerify("allwidgets.xml", "allwidgets_tab.png", ConfigGenerator.NEXUS_7_2012);
// We expect fidelity warnings for Path.isConvex. Fail for anything else.
sRenderMessages.removeIf(message -> message.equals("Path.isConvex is not supported."));
}
@Test
public void testActivityActionBar() throws ClassNotFoundException {
LayoutPullParser parser = createLayoutPullParser("simple_activity.xml");
LayoutLibTestCallback layoutLibCallback =
new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
layoutLibCallback.initResources();
SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
layoutLibCallback, "Theme.Material.Light.NoActionBar", false,
RenderingMode.V_SCROLL, 22);
renderAndVerify(params, "simple_activity_noactionbar.png");
parser = createLayoutPullParser("simple_activity.xml");
params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
layoutLibCallback, "Theme.Material.Light", false,
RenderingMode.V_SCROLL, 22);
renderAndVerify(params, "simple_activity.png");
// This also tests that a theme with "NoActionBar" DOES HAVE an action bar when we are
// displaying menus.
parser = createLayoutPullParser("simple_activity.xml");
params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
layoutLibCallback, "Theme.Material.Light.NoActionBar", false,
RenderingMode.V_SCROLL, 22);
params.setFlag(RenderParamsFlags.FLAG_KEY_ROOT_TAG, "menu");
renderAndVerify(params, "simple_activity.png");
}
@Test
public void testOnApplyInsetsCall()
throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
// We get the widget via reflection to avoid IntelliJ complaining about the class being
// located in the wrong package. (From the Bridge tests point of view, it is)
Class insetsWidgetClass = Class.forName("com.android.layoutlib.test.myapplication.widgets" +
".InsetsWidget");
Field field = insetsWidgetClass.getDeclaredField("sApplyInsetsCalled");
assertFalse((Boolean)field.get(null));
LayoutPullParser parser = createLayoutPullParser("insets.xml");
LayoutLibTestCallback layoutLibCallback =
new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
layoutLibCallback.initResources();
SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
layoutLibCallback, "Theme.Material.Light.NoActionBar", false,
RenderingMode.NORMAL, 22);
render(params, -1);
assertTrue((Boolean)field.get(null));
field.set(null, false);
}
@AfterClass
public static void tearDown() {
sLayoutLibLog = null;
sFrameworkRepo = null;
sProjectResources = null;
sLogger = null;
sBridge = null;
gc();
System.out.println("Objects still linked from the DelegateManager:");
DelegateManager.dump(System.out);
}
/** Test expand_layout.xml */
@Test
public void testExpand() throws ClassNotFoundException {
// Create the layout pull parser.
LayoutPullParser parser = createLayoutPullParser("expand_vert_layout.xml");
// Create LayoutLibCallback.
LayoutLibTestCallback layoutLibCallback =
new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
layoutLibCallback.initResources();
ConfigGenerator customConfigGenerator = new ConfigGenerator()
.setScreenWidth(300)
.setScreenHeight(20)
.setDensity(Density.XHIGH)
.setNavigation(Navigation.NONAV);
SessionParams params = getSessionParams(parser, customConfigGenerator,
layoutLibCallback, "Theme.Material.Light.NoActionBar.Fullscreen", false,
RenderingMode.V_SCROLL, 22);
renderAndVerify(params, "expand_vert_layout.png");
customConfigGenerator = new ConfigGenerator()
.setScreenWidth(20)
.setScreenHeight(300)
.setDensity(Density.XHIGH)
.setNavigation(Navigation.NONAV);
parser = createLayoutPullParser("expand_horz_layout.xml");
params = getSessionParams(parser, customConfigGenerator,
layoutLibCallback, "Theme.Material.Light.NoActionBar.Fullscreen", false,
RenderingMode.H_SCROLL, 22);
renderAndVerify(params, "expand_horz_layout.png");
}
/** Test indeterminate_progressbar.xml */
@Test
public void testVectorAnimation() throws ClassNotFoundException {
// Create the layout pull parser.
LayoutPullParser parser = createLayoutPullParser("indeterminate_progressbar.xml");
// Create LayoutLibCallback.
LayoutLibTestCallback layoutLibCallback =
new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
layoutLibCallback.initResources();
SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
RenderingMode.V_SCROLL, 22);
renderAndVerify(params, "animated_vector.png", TimeUnit.SECONDS.toNanos(2));
parser = createLayoutPullParser("indeterminate_progressbar.xml");
params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
RenderingMode.V_SCROLL, 22);
renderAndVerify(params, "animated_vector_1.png", TimeUnit.SECONDS.toNanos(3));
}
/**
* Test a vector drawable that uses trimStart and trimEnd. It also tests all the primitives
* for vector drawables (lines, moves and cubic and quadratic curves).
*/
@Test
public void testVectorDrawable() throws ClassNotFoundException {
// Create the layout pull parser.
LayoutPullParser parser = createLayoutPullParser("vector_drawable.xml");
// Create LayoutLibCallback.
LayoutLibTestCallback layoutLibCallback =
new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
layoutLibCallback.initResources();
SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
RenderingMode.V_SCROLL, 22);
renderAndVerify(params, "vector_drawable.png", TimeUnit.SECONDS.toNanos(2));
}
/**
* Regression test for http://b.android.com/91383 and http://b.android.com/203797
*/
@Test
public void testVectorDrawable91383() throws ClassNotFoundException {
// Create the layout pull parser.
LayoutPullParser parser = createLayoutPullParser("vector_drawable_android.xml");
// Create LayoutLibCallback.
LayoutLibTestCallback layoutLibCallback =
new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
layoutLibCallback.initResources();
SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
RenderingMode.V_SCROLL, 22);
renderAndVerify(params, "vector_drawable_91383.png", TimeUnit.SECONDS.toNanos(2));
}
/** Test activity.xml */
@Test
public void testScrollingAndMeasure() throws ClassNotFoundException {
// Create the layout pull parser.
LayoutPullParser parser = createLayoutPullParser("scrolled.xml");
// Create LayoutLibCallback.
LayoutLibTestCallback layoutLibCallback =
new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
layoutLibCallback.initResources();
SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
RenderingMode.V_SCROLL, 22);
params.setForceNoDecor();
params.setExtendedViewInfoMode(true);
// Do an only-measure pass
RenderSession session = sBridge.createSession(params);
session.measure();
RenderResult result = RenderResult.getFromSession(session);
assertNotNull(result);
assertNotNull(result.getResult());
assertTrue(result.getResult().isSuccess());
ViewInfo rootLayout = result.getRootViews().get(0);
// Check the first box in the main LinearLayout
assertEquals(-90, rootLayout.getChildren().get(0).getTop());
assertEquals(-30, rootLayout.getChildren().get(0).getLeft());
assertEquals(90, rootLayout.getChildren().get(0).getBottom());
assertEquals(150, rootLayout.getChildren().get(0).getRight());
// Check the first box within the nested LinearLayout
assertEquals(-450, rootLayout.getChildren().get(5).getChildren().get(0).getTop());
assertEquals(90, rootLayout.getChildren().get(5).getChildren().get(0).getLeft());
assertEquals(-270, rootLayout.getChildren().get(5).getChildren().get(0).getBottom());
assertEquals(690, rootLayout.getChildren().get(5).getChildren().get(0).getRight());
// Do a full render pass
parser = createLayoutPullParser("scrolled.xml");
params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
RenderingMode.V_SCROLL, 22);
params.setForceNoDecor();
params.setExtendedViewInfoMode(true);
result = renderAndVerify(params, "scrolled.png");
assertNotNull(result);
assertNotNull(result.getResult());
assertTrue(result.getResult().isSuccess());
}
@Test
public void testGetResourceNameVariants() throws Exception {
// Setup
// Create the layout pull parser for our resources (empty.xml can not be part of the test
// app as it won't compile).
LayoutPullParser parser = new LayoutPullParser("/empty.xml");
// Create LayoutLibCallback.
LayoutLibTestCallback layoutLibCallback =
new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
layoutLibCallback.initResources();
SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_4,
layoutLibCallback, "AppTheme", true, RenderingMode.NORMAL, 22);
AssetManager assetManager = AssetManager.getSystem();
DisplayMetrics metrics = new DisplayMetrics();
Configuration configuration = RenderAction.getConfiguration(params);
Resources resources = new Resources(assetManager, metrics, configuration);
resources.mLayoutlibCallback = params.getLayoutlibCallback();
resources.mContext =
new BridgeContext(params.getProjectKey(), metrics, params.getResources(),
params.getAssets(), params.getLayoutlibCallback(), configuration,
params.getTargetSdkVersion(), params.isRtlSupported());
// Test
assertEquals("android:style/ButtonBar",
resources.getResourceName(android.R.style.ButtonBar));
assertEquals("android", resources.getResourcePackageName(android.R.style.ButtonBar));
assertEquals("ButtonBar", resources.getResourceEntryName(android.R.style.ButtonBar));
assertEquals("style", resources.getResourceTypeName(android.R.style.ButtonBar));
int id = resources.mLayoutlibCallback.getResourceId(ResourceType.STRING, "app_name");
assertEquals("com.android.layoutlib.test.myapplication:string/app_name",
resources.getResourceName(id));
assertEquals("com.android.layoutlib.test.myapplication",
resources.getResourcePackageName(id));
assertEquals("string", resources.getResourceTypeName(id));
assertEquals("app_name", resources.getResourceEntryName(id));
}
@Test
public void testStringEscaping() throws Exception {
// Setup
// Create the layout pull parser for our resources (empty.xml can not be part of the test
// app as it won't compile).
LayoutPullParser parser = new LayoutPullParser("/empty.xml");
// Create LayoutLibCallback.
LayoutLibTestCallback layoutLibCallback =
new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
layoutLibCallback.initResources();
SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_4,
layoutLibCallback, "AppTheme", true, RenderingMode.NORMAL, 22);
AssetManager assetManager = AssetManager.getSystem();
DisplayMetrics metrics = new DisplayMetrics();
Configuration configuration = RenderAction.getConfiguration(params);
Resources resources = new Resources(assetManager, metrics, configuration);
resources.mLayoutlibCallback = params.getLayoutlibCallback();
resources.mContext =
new BridgeContext(params.getProjectKey(), metrics, params.getResources(),
params.getAssets(), params.getLayoutlibCallback(), configuration,
params.getTargetSdkVersion(), params.isRtlSupported());
int id = resources.mLayoutlibCallback.getResourceId(ResourceType.ARRAY, "string_array");
String[] strings = resources.getStringArray(id);
assertArrayEquals(
new String[]{"mystring", "Hello world!", "candidates", "Unknown", "?EC"},
strings);
assertTrue(sRenderMessages.isEmpty());
}
@NonNull
private LayoutPullParser createLayoutPullParser(String layoutPath) {
return new LayoutPullParser(APP_TEST_RES + "/layout/" + layoutPath);
}
@NonNull
private static RenderResult render(SessionParams params, long frameTimeNanos) {
// TODO: Set up action bar handler properly to test menu rendering.
// Create session params.
System_Delegate.setBootTimeNanos(TimeUnit.MILLISECONDS.toNanos(871732800000L));
System_Delegate.setNanosTime(TimeUnit.MILLISECONDS.toNanos(871732800000L));
RenderSession session = sBridge.createSession(params);
try {
if (frameTimeNanos != -1) {
session.setElapsedFrameTimeNanos(frameTimeNanos);
}
if (!session.getResult().isSuccess()) {
getLogger().error(session.getResult().getException(),
session.getResult().getErrorMessage());
}
// Render the session with a timeout of 50s.
Result renderResult = session.render(50000);
if (!renderResult.isSuccess()) {
getLogger().error(session.getResult().getException(),
session.getResult().getErrorMessage());
}
return RenderResult.getFromSession(session);
} finally {
session.dispose();
}
}
/**
* Create a new rendering session and test that rendering the given layout doesn't throw any
* exceptions and matches the provided image.
* <p>
* If frameTimeNanos is >= 0 a frame will be executed during the rendering. The time indicates
* how far in the future is.
*/
@Nullable
private static RenderResult renderAndVerify(SessionParams params, String goldenFileName, long
frameTimeNanos)
throws ClassNotFoundException {
RenderResult result = Main.render(params, frameTimeNanos);
try {
String goldenImagePath = APP_TEST_DIR + "/golden/" + goldenFileName;
assertNotNull(result.getImage());
ImageUtils.requireSimilar(goldenImagePath, result.getImage());
} catch (IOException e) {
getLogger().error(e, e.getMessage());
}
return result;
}
/**
* Create a new rendering session and test that rendering the given layout doesn't throw any
* exceptions and matches the provided image.
*/
@Nullable
private static RenderResult renderAndVerify(SessionParams params, String goldenFileName)
throws ClassNotFoundException {
return Main.renderAndVerify(params, goldenFileName, -1);
}
/**
* Create a new rendering session and test that rendering the given layout on nexus 5
* doesn't throw any exceptions and matches the provided image.
*/
@Nullable
private RenderResult renderAndVerify(String layoutFileName, String goldenFileName)
throws ClassNotFoundException {
return renderAndVerify(layoutFileName, goldenFileName, ConfigGenerator.NEXUS_5);
}
/**
* Create a new rendering session and test that rendering the given layout on given device
* doesn't throw any exceptions and matches the provided image.
*/
@Nullable
private RenderResult renderAndVerify(String layoutFileName, String goldenFileName,
ConfigGenerator deviceConfig)
throws ClassNotFoundException {
SessionParams params = createSessionParams(layoutFileName, deviceConfig);
return renderAndVerify(params, goldenFileName);
}
private SessionParams createSessionParams(String layoutFileName, ConfigGenerator deviceConfig)
throws ClassNotFoundException {
// Create the layout pull parser.
LayoutPullParser parser = createLayoutPullParser(layoutFileName);
// Create LayoutLibCallback.
LayoutLibTestCallback layoutLibCallback =
new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
layoutLibCallback.initResources();
// TODO: Set up action bar handler properly to test menu rendering.
// Create session params.
return getSessionParams(parser, deviceConfig,
layoutLibCallback, "AppTheme", true, RenderingMode.NORMAL, 22);
}
/**
* Uses Theme.Material and Target sdk version as 22.
*/
private SessionParams getSessionParams(LayoutPullParser layoutParser,
ConfigGenerator configGenerator, LayoutLibTestCallback layoutLibCallback,
String themeName, boolean isProjectTheme, RenderingMode renderingMode, int targetSdk) {
FolderConfiguration config = configGenerator.getFolderConfig();
ResourceResolver resourceResolver =
ResourceResolver.create(sProjectResources.getConfiguredResources(config),
sFrameworkRepo.getConfiguredResources(config),
themeName, isProjectTheme);
SessionParams sessionParams = new SessionParams(
layoutParser,
renderingMode,
null /*used for caching*/,
configGenerator.getHardwareConfig(),
resourceResolver,
layoutLibCallback,
0,
targetSdk,
getLayoutLog());
sessionParams.setFlag(RenderParamsFlags.FLAG_DO_NOT_RENDER_ON_CREATE, true);
return sessionParams;
}
private static LayoutLog getLayoutLog() {
if (sLayoutLibLog == null) {
sLayoutLibLog = new LayoutLog() {
@Override
public void warning(String tag, String message, Object data) {
System.out.println("Warning " + tag + ": " + message);
failWithMsg(message);
}
@Override
public void fidelityWarning(@Nullable String tag, String message,
Throwable throwable, Object data) {
System.out.println("FidelityWarning " + tag + ": " + message);
if (throwable != null) {
throwable.printStackTrace();
}
failWithMsg(message == null ? "" : message);
}
@Override
public void error(String tag, String message, Object data) {
System.out.println("Error " + tag + ": " + message);
failWithMsg(message);
}
@Override
public void error(String tag, String message, Throwable throwable, Object data) {
System.out.println("Error " + tag + ": " + message);
if (throwable != null) {
throwable.printStackTrace();
}
failWithMsg(message);
}
};
}
return sLayoutLibLog;
}
private static ILogger getLogger() {
if (sLogger == null) {
sLogger = new ILogger() {
@Override
public void error(Throwable t, @Nullable String msgFormat, Object... args) {
if (t != null) {
t.printStackTrace();
}
failWithMsg(msgFormat == null ? "" : msgFormat, args);
}
@Override
public void warning(@NonNull String msgFormat, Object... args) {
failWithMsg(msgFormat, args);
}
@Override
public void info(@NonNull String msgFormat, Object... args) {
// pass.
}
@Override
public void verbose(@NonNull String msgFormat, Object... args) {
// pass.
}
};
}
return sLogger;
}
private static void failWithMsg(@NonNull String msgFormat, Object... args) {
sRenderMessages.add(args == null ? msgFormat : String.format(msgFormat, args));
}
}