Merge "Close netlink socket when shutting down IpReachabilityMonitor" into mnc-dev
diff --git a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
index 87762a6..32ee9e8 100644
--- a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
+++ b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
@@ -53,9 +53,14 @@
      */
     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 eb5f597..59f07a7 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
@@ -138,8 +138,9 @@
 
     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
@@ -462,7 +463,21 @@
 
     @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 @@
             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 @@
 
 
                 // 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 @@
     /** 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 @@
             }
             returnTypes.add(binaryToInternalClassName(className));
         }
+
+        mInjectedMethodsMap = createInfo.getInjectedMethodsMap();
     }
 
     /**
@@ -337,7 +342,7 @@
         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 @@
             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 245cd61..deb94c4 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.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 @@
       return JAVA_PKG_CLASSES;
     }
 
+    @Override
     public Set<String> getExcludedClasses() {
         String[] refactoredClasses = getJavaPkgClasses();
         int count = refactoredClasses.length / 2 + EXCLUDED_CLASSES.length;
@@ -115,6 +118,12 @@
         excludedClasses.addAll(Arrays.asList(EXCLUDED_CLASSES));
         return excludedClasses;
     }
+
+    @Override
+    public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
+        return INJECTED_METHODS;
+    }
+
     //-----
 
     /**
@@ -286,5 +295,11 @@
     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 @@
      * 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 @@
      * 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 @@
      * 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();
 
-    public abstract Set<String> getExcludedClasses();
+    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();
+
+    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 @@
         // 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 @@
                     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 @@
                     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 @@
 
             @Override
             public void replace(MethodInformation mi) {
-                assert isNeeded(mi.owner, mi.name, mi.desc);
                 mi.owner = ANDROID_LOCALE_CLASS;
             }
         });
@@ -119,14 +116,13 @@
         // 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 @@
             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 @@
 
             @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 @@
         @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 @@
     }
 
     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 @@
 
 
 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.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 @@
                  // 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 @@
                  // 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 @@
                 // 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 @@
                 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 {