diff options
Diffstat (limited to 'layoutlib')
-rw-r--r-- | layoutlib/BUILD | 15 | ||||
-rw-r--r-- | layoutlib/intellij.android.layoutlib.iml | 1 | ||||
-rw-r--r-- | layoutlib/intellij.android.layoutlib.tests.iml | 14 | ||||
-rw-r--r-- | layoutlib/src/META-INF/layoutlib.xml (renamed from layoutlib/src/META-INF/plugin.xml) | 12 | ||||
-rw-r--r-- | layoutlib/src/com/android/layoutlib/LayoutlibClassLoader.java | 108 | ||||
-rw-r--r-- | layoutlib/src/com/android/layoutlib/LayoutlibProvider.kt | 30 | ||||
-rw-r--r-- | layoutlib/testSrc/com/android/layoutlib/LayoutlibClassLoaderTest.java | 87 | ||||
-rw-r--r-- | layoutlib/testSrc/com/android/layoutlib/LayoutlibPrebuiltTest.kt | 47 | ||||
-rw-r--r-- | layoutlib/testSrc/com/android/layoutlib/TestBuild.java | 41 |
9 files changed, 347 insertions, 8 deletions
diff --git a/layoutlib/BUILD b/layoutlib/BUILD index 1aefed9ec93..7e56559133a 100644 --- a/layoutlib/BUILD +++ b/layoutlib/BUILD @@ -10,6 +10,21 @@ iml_module( deps = [ "//prebuilts/studio/intellij-sdk:studio-sdk", "//tools/base/layoutlib-api:studio.android.sdktools.layoutlib-api[module]", + "//tools/adt/idea/layoutlib-loader:intellij.android.layoutlib-loader[module]", "//tools/adt/idea/.idea/libraries:layoutlib", ], ) + +# managed by go/iml_to_build +iml_module( + name = "intellij.android.layoutlib.tests", + iml_files = ["intellij.android.layoutlib.tests.iml"], + test_srcs = ["testSrc"], + visibility = ["//visibility:public"], + # do not sort: must match IML order + deps = [ + "//prebuilts/studio/intellij-sdk:studio-sdk", + "//tools/adt/idea/layoutlib:intellij.android.layoutlib[module, test]", + "//tools/adt/idea/.idea/libraries:layoutlib[test]", + ], +) diff --git a/layoutlib/intellij.android.layoutlib.iml b/layoutlib/intellij.android.layoutlib.iml index d6ed5c8a505..070e027727f 100644 --- a/layoutlib/intellij.android.layoutlib.iml +++ b/layoutlib/intellij.android.layoutlib.iml @@ -9,6 +9,7 @@ <orderEntry type="library" name="studio-sdk" level="project" /> <orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="module" module-name="android.sdktools.layoutlib-api" /> + <orderEntry type="module" module-name="intellij.android.layoutlib-loader" /> <orderEntry type="library" name="layoutlib" level="project" /> </component> </module>
\ No newline at end of file diff --git a/layoutlib/intellij.android.layoutlib.tests.iml b/layoutlib/intellij.android.layoutlib.tests.iml new file mode 100644 index 00000000000..a4ef8d3eeaa --- /dev/null +++ b/layoutlib/intellij.android.layoutlib.tests.iml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module type="JAVA_MODULE" version="4"> + <component name="NewModuleRootManager" inherit-compiler-output="true"> + <exclude-output /> + <content url="file://$MODULE_DIR$/testSrc"> + <sourceFolder url="file://$MODULE_DIR$/testSrc" isTestSource="true" /> + </content> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + <orderEntry type="library" name="studio-sdk" level="project" /> + <orderEntry type="module" module-name="intellij.android.layoutlib" scope="TEST" /> + <orderEntry type="library" scope="TEST" name="layoutlib" level="project" /> + </component> +</module>
\ No newline at end of file diff --git a/layoutlib/src/META-INF/plugin.xml b/layoutlib/src/META-INF/layoutlib.xml index 5b0b1b975f7..3cb64d86951 100644 --- a/layoutlib/src/META-INF/plugin.xml +++ b/layoutlib/src/META-INF/layoutlib.xml @@ -1,5 +1,5 @@ <!-- - ~ Copyright (C) 2019 The Android Open Source Project + ~ Copyright (C) 2021 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. @@ -14,12 +14,9 @@ ~ limitations under the License. --> <idea-plugin> - <id>com.android.layoutlib</id> - <name>Layoutlib</name> - <version>1.0</version> - <vendor>Google</vendor> - - <description>Provides a library for rendering Android resources</description> + <extensions defaultExtensionNs="com.android.tools.idea.layoutlib"> + <layoutLibraryProvider implementation="com.android.layoutlib.LayoutlibProvider"/> + </extensions> <application-components> <component> @@ -27,5 +24,4 @@ <headless-implementation-class/> </component> </application-components> - </idea-plugin>
\ No newline at end of file diff --git a/layoutlib/src/com/android/layoutlib/LayoutlibClassLoader.java b/layoutlib/src/com/android/layoutlib/LayoutlibClassLoader.java new file mode 100644 index 00000000000..35790f71062 --- /dev/null +++ b/layoutlib/src/com/android/layoutlib/LayoutlibClassLoader.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2017 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; + +import android.os._Original_Build; +import com.google.common.annotations.VisibleForTesting; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.text.StringUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.org.objectweb.asm.ClassReader; +import org.jetbrains.org.objectweb.asm.ClassWriter; +import org.jetbrains.org.objectweb.asm.commons.ClassRemapper; +import org.jetbrains.org.objectweb.asm.commons.Remapper; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Deque; +import java.util.LinkedList; +import java.util.function.BiConsumer; + +/** + * {@link ClassLoader} used for Layoutlib. Currently it only generates {@code android.os.Build} dynamically by copying the class in + * {@link _Original_Build}. + * By generating {@code android.os.Build} dynamically, we avoid to have it in the classpath of the plugins. Some plugins check for the + * existence of the class in order to detect if they are running Android. This is just a workaround for that. + */ +public class LayoutlibClassLoader extends ClassLoader { + private static final Logger LOG = Logger.getInstance(LayoutlibClassLoader.class); + + LayoutlibClassLoader(@NotNull ClassLoader parent) { + super(parent); + + // Define the android.os.Build and all inner classes by renaming everything in android.os._Original_Build + generate(_Original_Build.class, (className, classBytes) -> defineClass(className, classBytes, 0, classBytes.length)); + } + + @NotNull + private static String toBinaryClassName(@NotNull String name) { + return name.replace('.', '/'); + } + + @NotNull + private static String toClassName(@NotNull String name) { + return name.replace('/', '.'); + } + + /** + * Creates a copy of the passed class, replacing its name with "android.os.Build". + */ + @VisibleForTesting + static void generate(@NotNull Class<?> originalBuildClass, @NotNull BiConsumer<String, byte[]> defineClass) { + ClassLoader loader = originalBuildClass.getClassLoader(); + String originalBuildClassName = originalBuildClass.getName(); + String originalBuildBinaryClassName = toBinaryClassName(originalBuildClassName); + Deque<String> pendingClasses = new LinkedList<>(); + pendingClasses.push(originalBuildClassName); + + Remapper remapper = new Remapper() { + @Override + public String map(String typeName) { + if (typeName.startsWith(originalBuildBinaryClassName)) { + return "android/os/Build" + StringUtil.trimStart(typeName, originalBuildBinaryClassName); + } + + return typeName; + } + }; + + while (!pendingClasses.isEmpty()) { + String name = pendingClasses.pop(); + + String newName = "android.os.Build" + StringUtil.trimStart(name, originalBuildClassName); + String binaryName = toBinaryClassName(name); + + try (InputStream is = loader.getResourceAsStream(binaryName + ".class")) { + ClassWriter writer = new ClassWriter(0); + ClassReader reader = new ClassReader(is); + ClassRemapper classRemapper = new ClassRemapper(writer, remapper) { + @Override + public void visitInnerClass(String name, String outerName, String innerName, int access) { + if (outerName != null && outerName.startsWith(binaryName)) { + pendingClasses.push(toClassName(name)); + } + super.visitInnerClass(name, outerName, innerName, access); + } + }; + reader.accept(classRemapper, 0); + defineClass.accept(newName, writer.toByteArray()); + } + catch (IOException e) { + LOG.warn("Unable to define android.os.Build", e); + } + } + } +} diff --git a/layoutlib/src/com/android/layoutlib/LayoutlibProvider.kt b/layoutlib/src/com/android/layoutlib/LayoutlibProvider.kt new file mode 100644 index 00000000000..93d531ee89f --- /dev/null +++ b/layoutlib/src/com/android/layoutlib/LayoutlibProvider.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2021 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 + +import com.android.layoutlib.bridge.Bridge +import com.android.tools.idea.layoutlib.LayoutLibrary +import com.android.tools.idea.layoutlib.LayoutLibraryLoader + +class LayoutlibProvider : LayoutLibraryLoader.LayoutLibraryProvider() { + override fun getLibrary(): LayoutLibrary { + return LayoutLibrary.load(Bridge(), LayoutlibClassLoader(LayoutlibProvider::class.java.classLoader)) + } + + override fun getFrameworkRClass(): Class<*> { + return com.android.internal.R::class.java + } +}
\ No newline at end of file diff --git a/layoutlib/testSrc/com/android/layoutlib/LayoutlibClassLoaderTest.java b/layoutlib/testSrc/com/android/layoutlib/LayoutlibClassLoaderTest.java new file mode 100644 index 00000000000..f7919e750be --- /dev/null +++ b/layoutlib/testSrc/com/android/layoutlib/LayoutlibClassLoaderTest.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2017 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; + +import com.google.common.io.CharSource; +import org.jetbrains.org.objectweb.asm.ClassReader; +import org.jetbrains.org.objectweb.asm.util.TraceClassVisitor; +import org.junit.Assert; +import org.junit.Test; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +public class LayoutlibClassLoaderTest { + + /** + * Simplify the output from the ASM Textifier so we do not get the comments or Opcodes into the output + */ + private static String simplify(String s) { + try { + return CharSource.wrap(s).readLines().stream().filter(l -> !l.trim().isEmpty() && !l.startsWith(" ") && !l.trim().startsWith("//")) + .collect(Collectors.joining("\n")); + } + catch (IOException e) { + e.printStackTrace(); + return ""; + } + } + + + @Test + public void generateBuildFile() { + Map<String, String> definedClasses = new HashMap<>(); + LayoutlibClassLoader.generate(TestBuild.class, (name, classBytes) -> { + StringWriter writer = new StringWriter(); + ClassReader reader = new ClassReader(classBytes); + TraceClassVisitor visitor = new TraceClassVisitor(new PrintWriter(writer)); + reader.accept(visitor, 0); + + definedClasses.put(name, simplify(writer.toString())); + }); + + Assert.assertEquals(3, definedClasses.size()); // Outer class + 2 inner classes + Assert.assertEquals("public class android/os/Build {\n" + + " public static INNERCLASS android/os/Build$InnerClass2 android/os/Build InnerClass2\n" + + " public static INNERCLASS android/os/Build$InnerClass android/os/Build InnerClass\n" + + " public final static Ljava/lang/String; TEST_FIELD = \"TestValue\"\n" + + " public <init>()V\n" + + " private static privateMethod()Ljava/lang/String;\n" + + " public static getSerial()Ljava/lang/String;\n" + + "}", + definedClasses.get("android.os.Build")); + + Assert.assertEquals("public class android/os/Build$InnerClass {\n" + + " public static INNERCLASS android/os/Build$InnerClass android/os/Build InnerClass\n" + + " public final static Ljava/lang/String; TEST_INNER_FIELD = \"TestInnerValue\"\n" + + " public final static I INNER_VALUE = 1\n" + + " public <init>()V\n" + + "}", + definedClasses.get("android.os.Build$InnerClass")); + + Assert.assertEquals("public class android/os/Build$InnerClass2 {\n" + + " public static INNERCLASS android/os/Build$InnerClass2 android/os/Build InnerClass2\n" + + " public final static Ljava/lang/String; TEST_INNER_FIELD2\n" + + " public <init>()V\n" + + " static <clinit>()V\n" + + "}", + definedClasses.get("android.os.Build$InnerClass2")); + } +}
\ No newline at end of file diff --git a/layoutlib/testSrc/com/android/layoutlib/LayoutlibPrebuiltTest.kt b/layoutlib/testSrc/com/android/layoutlib/LayoutlibPrebuiltTest.kt new file mode 100644 index 00000000000..9d646de7e1c --- /dev/null +++ b/layoutlib/testSrc/com/android/layoutlib/LayoutlibPrebuiltTest.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2018 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 + +import com.android.layoutlib.bridge.BridgeConstants +import org.junit.Test +import java.net.URL +import java.util.jar.JarFile +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class LayoutPrebuiltTest { + // Regression test for b/109738602 + @Test + fun jarContents() { + val classUrl = BridgeConstants::class.java.getResource(BridgeConstants::class.simpleName + ".class") + assertEquals("jar", classUrl.protocol) + val jarUrl = URL(classUrl.path.substringBefore("!")) + val jarFile = JarFile(jarUrl.file) + val jarEntryNames = jarFile.entries().asSequence() + .map { it.name } + .toSet() + + // Sanity check to make sure the file contains some data + assertTrue(jarEntryNames.contains("android/R.class")) + assertTrue(jarEntryNames.contains("android/R\$layout.class")) + + // Check that the jar does not contain classes in sun.** or java.**zs + assertFalse(jarEntryNames.any { + it.startsWith("sun/") || it.startsWith("java/") + }) + } +}
\ No newline at end of file diff --git a/layoutlib/testSrc/com/android/layoutlib/TestBuild.java b/layoutlib/testSrc/com/android/layoutlib/TestBuild.java new file mode 100644 index 00000000000..5b03c78381b --- /dev/null +++ b/layoutlib/testSrc/com/android/layoutlib/TestBuild.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2017 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; + +public class TestBuild { + public static final String TEST_FIELD = "TestValue"; + + private static String privateMethod() { + return "SerialNumber"; + } + + public static String getSerial() { + return "#" + privateMethod(); + } + + public static class InnerClass { + public static final String TEST_INNER_FIELD = "TestInnerValue"; + public static final int INNER_VALUE = 1; + } + + public static class InnerClass2 { + public static final String TEST_INNER_FIELD2; + + static { + TEST_INNER_FIELD2 = "TestInnerValue2"; + } + } +} |