summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorDeepanshu Gupta <deepanshu@google.com>2015-08-26 20:18:09 +0000
committerAndroid Git Automerger <android-git-automerger@android.com>2015-08-26 20:18:09 +0000
commitaae9c9309ca5155124f740b10a40c68b09ed0ce6 (patch)
treea7d57b62c42fd346920156cd8bf1f79d2ba0e9fd /tools
parent522e67e4ac600fb19c18f55f0e51096af9ae37d3 (diff)
parent2f50403fe0a8a035757b047af00c7520fadb9015 (diff)
downloadframeworks_base-aae9c9309ca5155124f740b10a40c68b09ed0ce6.zip
frameworks_base-aae9c9309ca5155124f740b10a40c68b09ed0ce6.tar.gz
frameworks_base-aae9c9309ca5155124f740b10a40c68b09ed0ce6.tar.bz2
am 2f50403f: am afedbc47: Make Context.getClassLoader() work. [DO NOT MERGE]
* commit '2f50403fe0a8a035757b047af00c7520fadb9015': Make Context.getClassLoader() work. [DO NOT MERGE]
Diffstat (limited to 'tools')
-rw-r--r--tools/layoutlib/bridge/src/android/view/BridgeInflater.java7
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java19
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java1
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java2
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java11
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java15
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java30
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodRunnables.java52
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodsAdapter.java41
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java67
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java133
11 files changed, 347 insertions, 31 deletions
diff --git a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
index 2e649c3..b495e26 100644
--- a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
+++ b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
@@ -53,9 +53,14 @@ public final class BridgeInflater extends LayoutInflater {
*/
private static final String[] sClassPrefixList = {
"android.widget.",
- "android.webkit."
+ "android.webkit.",
+ "android.app."
};
+ public static String[] getClassPrefixList() {
+ return sClassPrefixList;
+ }
+
protected BridgeInflater(LayoutInflater original, Context newContext) {
super(original, newContext);
newContext = getBaseContext(newContext);
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index 2cbbeba..ac3a3ce 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -137,8 +137,9 @@ public final class BridgeContext extends Context {
private final Stack<BridgeXmlBlockParser> mParserStack = new Stack<BridgeXmlBlockParser>();
private SharedPreferences mSharedPreferences;
+ private ClassLoader mClassLoader;
- /**
+ /**
* @param projectKey An Object identifying the project. This is used for the cache mechanism.
* @param metrics the {@link DisplayMetrics}.
* @param renderResources the configured resources (both framework and projects) for this
@@ -461,7 +462,21 @@ public final class BridgeContext extends Context {
@Override
public ClassLoader getClassLoader() {
- return this.getClass().getClassLoader();
+ if (mClassLoader == null) {
+ mClassLoader = new ClassLoader(getClass().getClassLoader()) {
+ @Override
+ protected Class<?> findClass(String name) throws ClassNotFoundException {
+ for (String prefix : BridgeInflater.getClassPrefixList()) {
+ if (name.startsWith(prefix)) {
+ // These are framework classes and should not be loaded from the app.
+ throw new ClassNotFoundException(name + " not found");
+ }
+ }
+ return BridgeContext.this.mLayoutlibCallback.findClass(name);
+ }
+ };
+ }
+ return mClassLoader;
}
@Override
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java
index 2b95488..f3a0d58 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java
@@ -275,7 +275,6 @@ public abstract class RenderAction<T extends RenderParams> extends FrameworkReso
mContext.getRenderResources().setLogger(null);
}
ParserFactory.setParserFactory(null);
-
}
public static BridgeContext getCurrentContext() {
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
index aa51c46..c8b2b84 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
@@ -728,7 +728,7 @@ public class AsmAnalyzer {
// Check if method needs to replaced by a call to a different method.
- if (ReplaceMethodCallsAdapter.isReplacementNeeded(owner, name, desc)) {
+ if (ReplaceMethodCallsAdapter.isReplacementNeeded(owner, name, desc, mOwnerClass)) {
mReplaceMethodCallClasses.add(mOwnerClass);
}
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
index bd6f070..3aa7cdf 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
@@ -72,6 +72,9 @@ public class AsmGenerator {
/** FQCN Names of classes to refactor. All reference to old-FQCN will be updated to new-FQCN.
* map old-FQCN => new-FQCN */
private final HashMap<String, String> mRefactorClasses;
+ /** Methods to inject. FQCN of class in which method should be injected => runnable that does
+ * the injection. */
+ private final Map<String, ICreateInfo.InjectMethodRunnable> mInjectedMethodsMap;
/**
* Creates a new generator that can generate the output JAR with the stubbed classes.
@@ -165,6 +168,8 @@ public class AsmGenerator {
}
returnTypes.add(binaryToInternalClassName(className));
}
+
+ mInjectedMethodsMap = createInfo.getInjectedMethodsMap();
}
/**
@@ -337,7 +342,7 @@ public class AsmGenerator {
ClassVisitor cv = cw;
if (mReplaceMethodCallsClasses.contains(className)) {
- cv = new ReplaceMethodCallsAdapter(cv);
+ cv = new ReplaceMethodCallsAdapter(cv, className);
}
cv = new RefactorClassAdapter(cv, mRefactorClasses);
@@ -345,6 +350,10 @@ public class AsmGenerator {
cv = new RenameClassAdapter(cv, className, newName);
}
+ String binaryNewName = newName.replace('/', '.');
+ if (mInjectedMethodsMap.keySet().contains(binaryNewName)) {
+ cv = new InjectMethodsAdapter(cv, mInjectedMethodsMap.get(binaryNewName));
+ }
cv = new TransformClassAdapter(mLog, mStubMethods, mDeleteReturns.get(className),
newName, cv, stubNativesOnly);
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index 8f50c5d..3ae0363 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -26,7 +26,9 @@ import com.android.tools.layoutlib.java.System_Delegate;
import com.android.tools.layoutlib.java.UnsafeByteSequence;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.HashSet;
+import java.util.Map;
import java.util.Set;
/**
@@ -105,6 +107,7 @@ public final class CreateInfo implements ICreateInfo {
return JAVA_PKG_CLASSES;
}
+ @Override
public Set<String> getExcludedClasses() {
String[] refactoredClasses = getJavaPkgClasses();
int count = refactoredClasses.length / 2 + EXCLUDED_CLASSES.length;
@@ -115,6 +118,12 @@ public final class CreateInfo implements ICreateInfo {
excludedClasses.addAll(Arrays.asList(EXCLUDED_CLASSES));
return excludedClasses;
}
+
+ @Override
+ public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
+ return INJECTED_METHODS;
+ }
+
//-----
/**
@@ -287,5 +296,11 @@ public final class CreateInfo implements ICreateInfo {
private final static String[] DELETE_RETURNS =
new String[] {
null }; // separator, for next class/methods list.
+
+ private final static Map<String, InjectMethodRunnable> INJECTED_METHODS =
+ new HashMap<String, InjectMethodRunnable>(1) {{
+ put("android.content.Context",
+ InjectMethodRunnables.CONTEXT_GET_FRAMEWORK_CLASS_LOADER);
+ }};
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java
index e49a668..ac10639 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java
@@ -16,6 +16,9 @@
package com.android.tools.layoutlib.create;
+import org.objectweb.asm.ClassVisitor;
+
+import java.util.Map;
import java.util.Set;
/**
@@ -27,33 +30,33 @@ public interface ICreateInfo {
* Returns the list of class from layoutlib_create to inject in layoutlib.
* The list can be empty but must not be null.
*/
- public abstract Class<?>[] getInjectedClasses();
+ Class<?>[] getInjectedClasses();
/**
* Returns the list of methods to rewrite as delegates.
* The list can be empty but must not be null.
*/
- public abstract String[] getDelegateMethods();
+ String[] getDelegateMethods();
/**
* Returns the list of classes on which to delegate all native methods.
* The list can be empty but must not be null.
*/
- public abstract String[] getDelegateClassNatives();
+ String[] getDelegateClassNatives();
/**
* Returns The list of methods to stub out. Each entry must be in the form
* "package.package.OuterClass$InnerClass#MethodName".
* The list can be empty but must not be null.
*/
- public abstract String[] getOverriddenMethods();
+ String[] getOverriddenMethods();
/**
* Returns the list of classes to rename, must be an even list: the binary FQCN
* of class to replace followed by the new FQCN.
* The list can be empty but must not be null.
*/
- public abstract String[] getRenamedClasses();
+ String[] getRenamedClasses();
/**
* Returns the list of classes for which the methods returning them should be deleted.
@@ -62,7 +65,7 @@ public interface ICreateInfo {
* the methods to delete.
* The list can be empty but must not be null.
*/
- public abstract String[] getDeleteReturns();
+ String[] getDeleteReturns();
/**
* Returns the list of classes to refactor, must be an even list: the
@@ -70,7 +73,18 @@ public interface ICreateInfo {
* to the old class should be updated to the new class.
* The list can be empty but must not be null.
*/
- public abstract String[] getJavaPkgClasses();
+ String[] getJavaPkgClasses();
+
+ Set<String> getExcludedClasses();
+
+ /**
+ * Returns a map from binary FQCN className to {@link InjectMethodRunnable} which will be
+ * called to inject methods into a class.
+ * Can be empty but must not be null.
+ */
+ Map<String, InjectMethodRunnable> getInjectedMethodsMap();
- public abstract Set<String> getExcludedClasses();
+ abstract class InjectMethodRunnable {
+ public abstract void generateMethods(ClassVisitor cv);
+ }
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodRunnables.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodRunnables.java
new file mode 100644
index 0000000..39d46d7
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodRunnables.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2015 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.tools.layoutlib.create;
+
+import com.android.tools.layoutlib.create.ICreateInfo.InjectMethodRunnable;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+
+import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
+import static org.objectweb.asm.Opcodes.ALOAD;
+import static org.objectweb.asm.Opcodes.ARETURN;
+import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
+
+public class InjectMethodRunnables {
+ public static final ICreateInfo.InjectMethodRunnable CONTEXT_GET_FRAMEWORK_CLASS_LOADER
+ = new InjectMethodRunnable() {
+ @Override
+ public void generateMethods(ClassVisitor cv) {
+ // generated by compiling the class:
+ // class foo { public ClassLoader getFrameworkClassLoader() { return getClass().getClassLoader(); } }
+ // and then running ASMifier on it:
+ // java -classpath asm-debug-all-5.0.2.jar:. org.objectweb.asm.util.ASMifier foo
+ MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "getFrameworkClassLoader",
+ "()Ljava/lang/ClassLoader;", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "getClass",
+ "()Ljava/lang/Class;");
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getClassLoader",
+ "()Ljava/lang/ClassLoader;");
+ mv.visitInsn(ARETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ // generated code ends.
+ }
+ };
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodsAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodsAdapter.java
new file mode 100644
index 0000000..ea2b9c9
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/InjectMethodsAdapter.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 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.tools.layoutlib.create;
+
+import com.android.tools.layoutlib.create.ICreateInfo.InjectMethodRunnable;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Injects methods into some classes.
+ */
+public class InjectMethodsAdapter extends ClassVisitor {
+
+ private final ICreateInfo.InjectMethodRunnable mRunnable;
+
+ public InjectMethodsAdapter(ClassVisitor cv, InjectMethodRunnable runnable) {
+ super(Opcodes.ASM4, cv);
+ mRunnable = runnable;
+ }
+
+ @Override
+ public void visitEnd() {
+ mRunnable.generateMethods(this);
+ super.visitEnd();
+ }
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
index 384d8ca..4369148 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
@@ -62,14 +62,13 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor {
// Case 1: java.lang.System.arraycopy()
METHOD_REPLACERS.add(new MethodReplacer() {
@Override
- public boolean isNeeded(String owner, String name, String desc) {
+ public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
return JAVA_LANG_SYSTEM.equals(owner) && "arraycopy".equals(name) &&
ARRAYCOPY_DESCRIPTORS.contains(desc);
}
@Override
public void replace(MethodInformation mi) {
- assert isNeeded(mi.owner, mi.name, mi.desc);
mi.desc = "(Ljava/lang/Object;ILjava/lang/Object;II)V";
}
});
@@ -81,14 +80,13 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor {
Type.getMethodDescriptor(STRING, Type.getType(Locale.class));
@Override
- public boolean isNeeded(String owner, String name, String desc) {
+ public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
return JAVA_LOCALE_CLASS.equals(owner) && "()Ljava/lang/String;".equals(desc) &&
("toLanguageTag".equals(name) || "getScript".equals(name));
}
@Override
public void replace(MethodInformation mi) {
- assert isNeeded(mi.owner, mi.name, mi.desc);
mi.opcode = Opcodes.INVOKESTATIC;
mi.owner = ANDROID_LOCALE_CLASS;
mi.desc = LOCALE_TO_STRING;
@@ -103,7 +101,7 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor {
Type.getType(Locale.class), STRING);
@Override
- public boolean isNeeded(String owner, String name, String desc) {
+ public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
return JAVA_LOCALE_CLASS.equals(owner) &&
("adjustLanguageCode".equals(name) && desc.equals(STRING_TO_STRING) ||
"forLanguageTag".equals(name) && desc.equals(STRING_TO_LOCALE));
@@ -111,7 +109,6 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor {
@Override
public void replace(MethodInformation mi) {
- assert isNeeded(mi.owner, mi.name, mi.desc);
mi.owner = ANDROID_LOCALE_CLASS;
}
});
@@ -119,14 +116,13 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor {
// Case 4: java.lang.System.log?()
METHOD_REPLACERS.add(new MethodReplacer() {
@Override
- public boolean isNeeded(String owner, String name, String desc) {
+ public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
return JAVA_LANG_SYSTEM.equals(owner) && name.length() == 4
&& name.startsWith("log");
}
@Override
public void replace(MethodInformation mi) {
- assert isNeeded(mi.owner, mi.name, mi.desc);
assert mi.desc.equals("(Ljava/lang/String;Ljava/lang/Throwable;)V")
|| mi.desc.equals("(Ljava/lang/String;)V");
mi.name = "log";
@@ -142,7 +138,7 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor {
private final String LINKED_HASH_MAP = Type.getInternalName(LinkedHashMap.class);
@Override
- public boolean isNeeded(String owner, String name, String desc) {
+ public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
return LINKED_HASH_MAP.equals(owner) &&
"eldest".equals(name) &&
VOID_TO_MAP_ENTRY.equals(desc);
@@ -150,26 +146,64 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor {
@Override
public void replace(MethodInformation mi) {
- assert isNeeded(mi.owner, mi.name, mi.desc);
mi.opcode = Opcodes.INVOKESTATIC;
mi.owner = Type.getInternalName(LinkedHashMap_Delegate.class);
mi.desc = Type.getMethodDescriptor(
Type.getType(Map.Entry.class), Type.getType(LinkedHashMap.class));
}
});
+
+ // Case 6: android.content.Context.getClassLoader() in LayoutInflater
+ METHOD_REPLACERS.add(new MethodReplacer() {
+ // When LayoutInflater asks for a class loader, we must return the class loader that
+ // cannot return app's custom views/classes. This is so that in case of any failure
+ // or exception when instantiating the views, the IDE can replace it with a mock view
+ // and have proper error handling. However, if a custom view asks for the class
+ // loader, we must return a class loader that can find app's custom views as well.
+ // Thus, we rewrite the call to get class loader in LayoutInflater to
+ // getFrameworkClassLoader and inject a new method in Context. This leaves the normal
+ // method: Context.getClassLoader() free to be used by the apps.
+ private final String VOID_TO_CLASS_LOADER =
+ Type.getMethodDescriptor(Type.getType(ClassLoader.class));
+
+ @Override
+ public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
+ return owner.equals("android/content/Context") &&
+ sourceClass.equals("android/view/LayoutInflater") &&
+ name.equals("getClassLoader") &&
+ desc.equals(VOID_TO_CLASS_LOADER);
+ }
+
+ @Override
+ public void replace(MethodInformation mi) {
+ mi.name = "getFrameworkClassLoader";
+ }
+ });
}
- public static boolean isReplacementNeeded(String owner, String name, String desc) {
+ /**
+ * If a method some.package.Class.Method(args) is called from some.other.Class,
+ * @param owner some/package/Class
+ * @param name Method
+ * @param desc (args)returnType
+ * @param sourceClass some/other/Class
+ * @return if the method invocation needs to be replaced by some other class.
+ */
+ public static boolean isReplacementNeeded(String owner, String name, String desc,
+ String sourceClass) {
for (MethodReplacer replacer : METHOD_REPLACERS) {
- if (replacer.isNeeded(owner, name, desc)) {
+ if (replacer.isNeeded(owner, name, desc, sourceClass)) {
return true;
}
}
return false;
}
- public ReplaceMethodCallsAdapter(ClassVisitor cv) {
+ private final String mOriginalClassName;
+
+ public ReplaceMethodCallsAdapter(ClassVisitor cv, String originalClassName) {
super(Opcodes.ASM4, cv);
+ mOriginalClassName = originalClassName;
}
@Override
@@ -187,7 +221,7 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor {
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc) {
for (MethodReplacer replacer : METHOD_REPLACERS) {
- if (replacer.isNeeded(owner, name, desc)) {
+ if (replacer.isNeeded(owner, name, desc, mOriginalClassName)) {
MethodInformation mi = new MethodInformation(opcode, owner, name, desc);
replacer.replace(mi);
opcode = mi.opcode;
@@ -216,13 +250,12 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor {
}
private interface MethodReplacer {
- public boolean isNeeded(String owner, String name, String desc);
+ boolean isNeeded(String owner, String name, String desc, String sourceClass);
/**
* Updates the MethodInformation with the new values of the method attributes -
* opcode, owner, name and desc.
- *
*/
- public void replace(MethodInformation mi);
+ void replace(MethodInformation mi);
}
}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
index cf91386..2c21470 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
@@ -19,7 +19,9 @@ package com.android.tools.layoutlib.create;
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 org.junit.After;
@@ -32,13 +34,17 @@ import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@@ -130,6 +136,11 @@ public class AsmGeneratorTest {
// methods deleted from their return type.
return new String[0];
}
+
+ @Override
+ public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
+ return new HashMap<String, InjectMethodRunnable>(0);
+ }
};
AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
@@ -200,6 +211,11 @@ public class AsmGeneratorTest {
// methods deleted from their return type.
return new String[0];
}
+
+ @Override
+ public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
+ return new HashMap<String, InjectMethodRunnable>(0);
+ }
};
AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
@@ -278,6 +294,11 @@ public class AsmGeneratorTest {
// methods deleted from their return type.
return new String[0];
}
+
+ @Override
+ public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
+ return new HashMap<String, InjectMethodRunnable>(0);
+ }
};
AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
@@ -303,6 +324,118 @@ public class AsmGeneratorTest {
filesFound.keySet().toArray());
}
+ @Test
+ public void testMethodInjection() throws IOException, LogAbortException,
+ ClassNotFoundException, IllegalAccessException, InstantiationException,
+ NoSuchMethodException, InvocationTargetException {
+ ICreateInfo ci = new ICreateInfo() {
+ @Override
+ public Class<?>[] getInjectedClasses() {
+ return new Class<?>[0];
+ }
+
+ @Override
+ public String[] getDelegateMethods() {
+ return new String[0];
+ }
+
+ @Override
+ public String[] getDelegateClassNatives() {
+ return new String[0];
+ }
+
+ @Override
+ public String[] getOverriddenMethods() {
+ // methods to force override
+ return new String[0];
+ }
+
+ @Override
+ public String[] getRenamedClasses() {
+ // classes to rename (so that we can replace them)
+ return new String[0];
+ }
+
+ @Override
+ public String[] getJavaPkgClasses() {
+ // classes to refactor (so that we can replace them)
+ return new String[0];
+ }
+
+ @Override
+ public Set<String> getExcludedClasses() {
+ return new HashSet<String>(0);
+ }
+
+ @Override
+ public String[] getDeleteReturns() {
+ // methods deleted from their return type.
+ return new String[0];
+ }
+
+ @Override
+ public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
+ HashMap<String, InjectMethodRunnable> map =
+ new HashMap<String, InjectMethodRunnable>(1);
+ map.put("mock_android.util.EmptyArray",
+ InjectMethodRunnables.CONTEXT_GET_FRAMEWORK_CLASS_LOADER);
+ return map;
+ }
+ };
+
+ AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
+ AsmAnalyzer aa = new AsmAnalyzer(mLog, mOsJarPath, agen,
+ null, // derived from
+ new String[] { // include classes
+ "**"
+ },
+ ci.getExcludedClasses(),
+ new String[] { /* include files */
+ "mock_android/data/data*"
+ });
+ aa.analyze();
+ agen.generate();
+ Map<String, ClassReader> output = new TreeMap<String, ClassReader>();
+ Map<String, InputStream> filesFound = new TreeMap<String, InputStream>();
+ parseZip(mOsDestJar, output, filesFound);
+ final String modifiedClass = "mock_android.util.EmptyArray";
+ final String modifiedClassPath = modifiedClass.replace('.', '/').concat(".class");
+ ZipFile zipFile = new ZipFile(mOsDestJar);
+ ZipEntry entry = zipFile.getEntry(modifiedClassPath);
+ assertNotNull(entry);
+ final byte[] bytes;
+ final InputStream inputStream = zipFile.getInputStream(entry);
+ try {
+ bytes = getByteArray(inputStream);
+ } finally {
+ inputStream.close();
+ }
+ ClassLoader classLoader = new ClassLoader(getClass().getClassLoader()) {
+ @Override
+ protected Class<?> findClass(String name) throws ClassNotFoundException {
+ if (name.equals(modifiedClass)) {
+ return defineClass(null, bytes, 0, bytes.length);
+ }
+ throw new ClassNotFoundException(name + " not found.");
+ }
+ };
+ Class<?> emptyArrayClass = classLoader.loadClass(modifiedClass);
+ Object emptyArrayInstance = emptyArrayClass.newInstance();
+ Method method = emptyArrayClass.getMethod("getFrameworkClassLoader");
+ Object cl = method.invoke(emptyArrayInstance);
+ assertEquals(classLoader, cl);
+ }
+
+ private static byte[] getByteArray(InputStream stream) throws IOException {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ byte[] buffer = new byte[1024];
+ int read;
+ while ((read = stream.read(buffer, 0, buffer.length)) > -1) {
+ bos.write(buffer, 0, read);
+ }
+ return bos.toByteArray();
+ }
+
private void parseZip(String jarPath,
Map<String, ClassReader> classes,
Map<String, InputStream> filesFound) throws IOException {