Merge "Merge Android Pie into master"
diff --git a/Android.bp b/Android.bp
index 245d3e8..61b24ec 100644
--- a/Android.bp
+++ b/Android.bp
@@ -41,7 +41,7 @@
 // Build dispatcher for Dexmaker's inline MockMaker
 java_library_static {
     name: "dexmaker-inline-mockmaker-dispatcher",
-    sdk_version: "25",
+    sdk_version: "current",
     srcs: ["dexmaker-mockito-inline-dispatcher/src/main/java/**/*.java"],
 }
 
@@ -107,7 +107,7 @@
 // Build Dexmaker's inline MockMaker, a plugin to Mockito
 java_library_static {
     name: "dexmaker-inline-mockmaker",
-    sdk_version: "25",
+    sdk_version: "current",
     srcs: ["dexmaker-mockito-inline/src/main/java/**/*.java"],
     java_resource_dirs: ["dexmaker-mockito-inline/src/main/resources"],
     libs: [
@@ -115,6 +115,12 @@
         "mockito-api",
     ],
     required: ["libdexmakerjvmtiagent"],
+
+    errorprone: {
+        javacflags: [
+            "-Xep:CollectionIncompatibleType:WARN"
+        ],
+    }
 }
 
 java_import {
diff --git a/README.version b/README.version
index 0b3b59a..92d3d64 100644
--- a/README.version
+++ b/README.version
@@ -1,5 +1,5 @@
 URL: https://github.com/linkedin/dexmaker/
-Version: master (fce01046a9519f8c1e5fd826fe5169eb600710ad)
+Version: master (5fb49bba98647d7a0aeea0cbf91fd670c3ff552a)
 License: Apache 2.0
 Description:
 Dexmaker is a Java-language API for doing compile time or runtime code generation targeting the Dalvik VM. Unlike cglib or ASM, this library creates Dalvik .dex files instead of Java .class files.
@@ -10,4 +10,5 @@
 
 Local Modifications:
         Allow to share classloader via dexmaker.share_classloader system property (I8c2490c3ec8e8582dc41c486f8f7a406bd635ebb)
-	Use new attach API (I2be3ac3218c11f2ccf9a675fffd35f4fab548070)
+	Allow 'Q' until we can replace the version check with a number based check
+        Mark mocks as trusted (needs upstreaming)
diff --git a/dexmaker-mockito-inline-tests/src/androidTest/java/com/android/dx/mockito/inline/tests/MultipleJvmtiAgentsInterference.java b/dexmaker-mockito-inline-tests/src/androidTest/java/com/android/dx/mockito/inline/tests/MultipleJvmtiAgentsInterference.java
index d66b128..4f84276 100644
--- a/dexmaker-mockito-inline-tests/src/androidTest/java/com/android/dx/mockito/inline/tests/MultipleJvmtiAgentsInterference.java
+++ b/dexmaker-mockito-inline-tests/src/androidTest/java/com/android/dx/mockito/inline/tests/MultipleJvmtiAgentsInterference.java
@@ -17,24 +17,18 @@
 package com.android.dx.mockito.inline.tests;
 
 import android.os.Build;
+import android.os.Debug;
 
+import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-import dalvik.system.BaseDexClassLoader;
-
 import static org.junit.Assert.assertNull;
 import static org.junit.Assume.assumeTrue;
 import static org.mockito.Mockito.mock;
 
 public class MultipleJvmtiAgentsInterference {
-    private static final String AGENT_LIB_NAME = "multiplejvmtiagentsinterferenceagent";
+    private static final String AGENT_LIB_NAME = "libmultiplejvmtiagentsinterferenceagent.so";
 
     public class TestClass {
         public String returnA() {
@@ -44,32 +38,10 @@
 
     @BeforeClass
     public static void installTestAgent() throws Exception {
-        // TODO (moltmann@google.com): Replace with proper check for >= P
-        assumeTrue(Build.VERSION.CODENAME.equals("P"));
+        assumeTrue(Build.VERSION.SDK_INT >= 28);
 
-        // Currently Debug.attachJvmtiAgent requires a file in the right directory
-        File copiedAgent = File.createTempFile("testagent", ".so");
-        copiedAgent.deleteOnExit();
-
-        try (InputStream is = new FileInputStream(((BaseDexClassLoader)
-                MultipleJvmtiAgentsInterference.class.getClassLoader()).findLibrary
-                (AGENT_LIB_NAME))) {
-            try (OutputStream os = new FileOutputStream(copiedAgent)) {
-                byte[] buffer = new byte[64 * 1024];
-
-                while (true) {
-                    int numRead = is.read(buffer);
-                    if (numRead == -1) {
-                        break;
-                    }
-                    os.write(buffer, 0, numRead);
-                }
-            }
-        }
-
-        // TODO (moltmann@google.com): Replace with regular method call once the API becomes public
-        Class.forName("android.os.Debug").getMethod("attachJvmtiAgent", String.class, String
-                .class).invoke(null, copiedAgent.getAbsolutePath(), null);
+        Debug.attachJvmtiAgent(AGENT_LIB_NAME, null,
+                MultipleJvmtiAgentsInterference.class.getClassLoader());
     }
 
     @Test
@@ -87,5 +59,11 @@
         assertNull(t.returnA());
     }
 
+    @AfterClass
+    public static void DisableRetransfromHook() {
+        disableRetransformHook();
+    }
+
     private native int nativeRetransformClasses(Class<?>[] classes);
+    private static native int disableRetransformHook();
 }
diff --git a/dexmaker-mockito-inline-tests/src/main/jni/multiplejvmtiagentsinterferenceagent/agent.cc b/dexmaker-mockito-inline-tests/src/main/jni/multiplejvmtiagentsinterferenceagent/agent.cc
index a293fe7..b1c5455 100644
--- a/dexmaker-mockito-inline-tests/src/main/jni/multiplejvmtiagentsinterferenceagent/agent.cc
+++ b/dexmaker-mockito-inline-tests/src/main/jni/multiplejvmtiagentsinterferenceagent/agent.cc
@@ -22,9 +22,9 @@
 
 #include "jvmti.h"
 
-#include <dex_ir.h>
-#include <writer.h>
-#include <reader.h>
+#include <slicer/dex_ir.h>
+#include <slicer/writer.h>
+#include <slicer/reader.h>
 
 using namespace dex;
 
@@ -148,4 +148,15 @@
 
         return error;
     }
-}
\ No newline at end of file
+
+    // Disable hook to not slow down test
+    extern "C" JNIEXPORT jint JNICALL
+    Java_com_android_dx_mockito_inline_tests_MultipleJvmtiAgentsInterference_disableRetransformHook(
+            JNIEnv *env,
+            jclass ignored) {
+        return localJvmtiEnv->SetEventNotificationMode(JVMTI_DISABLE,
+                                                       JVMTI_EVENT_CLASS_FILE_LOAD_HOOK,
+                                                       NULL);
+
+    }
+}
diff --git a/dexmaker-mockito-inline/CMakeLists.txt b/dexmaker-mockito-inline/CMakeLists.txt
index cd26d58..b700cd4 100644
--- a/dexmaker-mockito-inline/CMakeLists.txt
+++ b/dexmaker-mockito-inline/CMakeLists.txt
@@ -20,7 +20,7 @@
             STATIC
             ${slicer_sources})
 
-include_directories(external/jdk external/slicer/)
+include_directories(external/jdk external/slicer/export/)
 
 target_link_libraries(slicer z)
 
diff --git a/dexmaker-mockito-inline/build.gradle b/dexmaker-mockito-inline/build.gradle
index 54e85ec..853f061 100644
--- a/dexmaker-mockito-inline/build.gradle
+++ b/dexmaker-mockito-inline/build.gradle
@@ -1,7 +1,7 @@
 apply plugin: 'com.android.library'
 
 android {
-    compileSdkVersion 25
+    compileSdkVersion 'android-P'
     buildToolsVersion "25.0.0"
 
     lintOptions {
@@ -28,6 +28,6 @@
 
 dependencies {
     compile project(':dexmaker')
-    compile 'org.mockito:mockito-core:2.12.0'
+    compile 'org.mockito:mockito-core:2.15.0', { exclude group: "net.bytebuddy" }
 }
 
diff --git a/dexmaker-mockito-inline/src/main/java/com/android/dx/mockito/inline/ClassTransformer.java b/dexmaker-mockito-inline/src/main/java/com/android/dx/mockito/inline/ClassTransformer.java
index f719304..c702b2f 100644
--- a/dexmaker-mockito-inline/src/main/java/com/android/dx/mockito/inline/ClassTransformer.java
+++ b/dexmaker-mockito-inline/src/main/java/com/android/dx/mockito/inline/ClassTransformer.java
@@ -17,15 +17,15 @@
 package com.android.dx.mockito.inline;
 
 import org.mockito.exceptions.base.MockitoException;
-import org.mockito.internal.util.concurrent.WeakConcurrentMap;
-import org.mockito.internal.util.concurrent.WeakConcurrentSet;
 
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.security.ProtectionDomain;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
+import java.util.Map;
 import java.util.Random;
 import java.util.Set;
 
@@ -65,7 +65,7 @@
     private final JvmtiAgent agent;
 
     /** Types that have already be transformed */
-    private final WeakConcurrentSet<Class<?>> mockedTypes;
+    private final Set<Class<?>> mockedTypes;
 
     /**
      * A unique identifier that is baked into the transformed classes. The entry hooks will then
@@ -96,9 +96,9 @@
      *              mocked or not.
      */
     ClassTransformer(JvmtiAgent agent, Class dispatcherClass,
-                     WeakConcurrentMap<Object, InvocationHandlerAdapter> mocks) {
+                     Map<Object, InvocationHandlerAdapter> mocks) {
         this.agent = agent;
-        mockedTypes = new WeakConcurrentSet<>(WeakConcurrentSet.Cleaner.INLINE);
+        mockedTypes = Collections.synchronizedSet(new HashSet<Class<?>>());
         identifier = Long.toString(random.nextLong());
         MockMethodAdvice advice = new MockMethodAdvice(mocks);
 
@@ -186,5 +186,16 @@
         }
     }
 
+    /**
+     * Check if the class should be transformed.
+     *
+     * @param classBeingRedefined The class that might need to transformed
+     *
+     * @return {@code true} iff the class needs to be transformed
+     */
+    boolean shouldTransform(Class<?> classBeingRedefined) {
+        return classBeingRedefined != null && mockedTypes.contains(classBeingRedefined);
+    }
+
     private native byte[] nativeRedefine(String identifier, byte[] original);
 }
diff --git a/dexmaker-mockito-inline/src/main/java/com/android/dx/mockito/inline/InlineDexmakerMockMaker.java b/dexmaker-mockito-inline/src/main/java/com/android/dx/mockito/inline/InlineDexmakerMockMaker.java
index d5be235..abcf4f5 100644
--- a/dexmaker-mockito-inline/src/main/java/com/android/dx/mockito/inline/InlineDexmakerMockMaker.java
+++ b/dexmaker-mockito-inline/src/main/java/com/android/dx/mockito/inline/InlineDexmakerMockMaker.java
@@ -16,24 +16,36 @@
 
 package com.android.dx.mockito.inline;
 
+import android.os.AsyncTask;
+import android.os.Build;
+import android.util.ArraySet;
+
 import com.android.dx.stock.ProxyBuilder;
 import com.android.dx.stock.ProxyBuilder.MethodSetEntry;
 
+import org.mockito.Mockito;
 import org.mockito.exceptions.base.MockitoException;
-import org.mockito.internal.configuration.plugins.Plugins;
 import org.mockito.internal.creation.instance.Instantiator;
-import org.mockito.internal.util.Platform;
-import org.mockito.internal.util.concurrent.WeakConcurrentMap;
+import org.mockito.internal.util.reflection.LenientCopyTool;
 import org.mockito.invocation.MockHandler;
 import org.mockito.mock.MockCreationSettings;
+import org.mockito.plugins.InstantiatorProvider;
 import org.mockito.plugins.MockMaker;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.lang.reflect.Proxy;
+import java.util.AbstractMap;
+import java.util.Collection;
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -96,6 +108,40 @@
                         + "Potentially, the current VM does not support the jvmti API correctly",
                         ioe);
             }
+
+            // Blacklisted APIs were introduced in Android P:
+            //
+            // https://android-developers.googleblog.com/2018/02/
+            // improving-stability-by-reducing-usage.html
+            //
+            // This feature prevents access to blacklisted fields and calling of blacklisted APIs
+            // if the calling class is not trusted.
+            Method allowHiddenApiReflectionFrom;
+            try {
+                Class vmDebug = Class.forName("dalvik.system.VMDebug");
+                allowHiddenApiReflectionFrom = vmDebug.getDeclaredMethod(
+                        "allowHiddenApiReflectionFrom", Class.class);
+            } catch (ClassNotFoundException | NoSuchMethodException e) {
+                throw new IllegalStateException("Cannot find "
+                        + "VMDebug#allowHiddenApiReflectionFrom.");
+            }
+
+            // The LenientCopyTool copies the fields to a spy when creating the copy from an
+            // existing object. Some of the fields might be blacklisted. Marking the LenientCopyTool
+            // as trusted allows the tool to copy all fields, including the blacklisted ones.
+            try {
+                allowHiddenApiReflectionFrom.invoke(null, LenientCopyTool.class);
+            } catch (InvocationTargetException e) {
+                throw e.getCause();
+            }
+
+            // The MockMethodAdvice is used by methods of spies to call the real methods. As the
+            // real methods might be blacklisted, this class needs to be marked as trusted.
+            try {
+                allowHiddenApiReflectionFrom.invoke(null, MockMethodAdvice.class);
+            } catch (InvocationTargetException e) {
+                throw e.getCause();
+            }
         } catch (Throwable throwable) {
             agent = null;
             initializationError = throwable;
@@ -111,7 +157,7 @@
      * modified, some are not. This list helps the {@link MockMethodAdvice} help figure out if a
      * object's method calls should be intercepted.
      */
-    private final WeakConcurrentMap<Object, InvocationHandlerAdapter> mocks;
+    private final Map<Object, InvocationHandlerAdapter> mocks;
 
     /**
      * Class doing the actual byte code transformation.
@@ -126,10 +172,11 @@
             throw new RuntimeException(
                     "Could not initialize inline mock maker.\n"
                     + "\n"
-                    + Platform.describe(), INITIALIZATION_ERROR);
+                    + "Release: Android " + Build.VERSION.RELEASE + " " + Build.VERSION.INCREMENTAL
+                    + "Device: " + Build.BRAND + " " + Build.MODEL, INITIALIZATION_ERROR);
         }
 
-        mocks = new WeakConcurrentMap.WithInlinedExpunction<>();
+        mocks = new MockMap();
         classTransformer = new ClassTransformer(AGENT, DISPATCHER_CLASS, mocks);
     }
 
@@ -212,7 +259,8 @@
 
             Class<? extends T> proxyClass;
 
-            Instantiator instantiator = Plugins.getInstantiatorProvider().getInstantiator(settings);
+            Instantiator instantiator = Mockito.framework().getPlugins()
+                    .getDefaultPlugin(InstantiatorProvider.class).getInstantiator(settings);
 
             if (subclassingRequired) {
                 try {
@@ -299,4 +347,336 @@
 
         return mocks.get(instance);
     }
+
+    /**
+     * A map mock -> adapter that holds weak references to the mocks and cleans them up when a
+     * stale reference is found.
+     */
+    private static class MockMap extends ReferenceQueue<Object>
+            implements Map<Object, InvocationHandlerAdapter> {
+        private static final int MIN_CLEAN_INTERVAL_MILLIS = 16000;
+        private static final int MAX_GET_WITHOUT_CLEAN = 16384;
+
+        private final Object lock = new Object();
+        private static StrongKey cachedKey;
+
+        private HashMap<WeakKey, InvocationHandlerAdapter> adapters = new HashMap<>();
+
+        /**
+         * The time we issues the last cleanup
+         */
+        long mLastCleanup = 0;
+
+        /**
+         * If {@link #cleanStaleReferences} is currently cleaning stale references out of
+         * {@link #adapters}
+         */
+        private boolean isCleaning = false;
+
+        /**
+         * The number of time {@link #get} was called without cleaning up stale references.
+         * {@link #get} is a method that is called often.
+         *
+         * We need to do periodic cleanups as we might never look at mocks at higher indexes and
+         * hence never realize that their references are stale.
+         */
+        private int getCount = 0;
+
+        /**
+         * Try to get a recycled cached key.
+         *
+         * @param obj the reference the key wraps
+         *
+         * @return The recycled cached key or a new one
+         */
+        private StrongKey createStrongKey(Object obj) {
+            synchronized (lock) {
+                if (cachedKey == null) {
+                    cachedKey = new StrongKey();
+                }
+
+                cachedKey.obj = obj;
+                StrongKey newKey = cachedKey;
+                cachedKey = null;
+
+                return newKey;
+            }
+        }
+
+        /**
+         * Recycle a key. The key should not be used afterwards
+         *
+         * @param key The key to recycle
+         */
+        private void recycleStrongKey(StrongKey key) {
+            synchronized (lock) {
+                cachedKey = key;
+            }
+        }
+
+        @Override
+        public int size() {
+            return adapters.size();
+        }
+
+        @Override
+        public boolean isEmpty() {
+            return adapters.isEmpty();
+        }
+
+        @Override
+        public boolean containsKey(Object mock) {
+            synchronized (lock) {
+                StrongKey key = createStrongKey(mock);
+                boolean containsKey = adapters.containsKey(key);
+                recycleStrongKey(key);
+
+                return containsKey;
+            }
+        }
+
+        @Override
+        public boolean containsValue(Object adapter) {
+            synchronized (lock) {
+                return adapters.containsValue(adapter);
+            }
+        }
+
+        @Override
+        public InvocationHandlerAdapter get(Object mock) {
+            synchronized (lock) {
+                if (getCount > MAX_GET_WITHOUT_CLEAN) {
+                    cleanStaleReferences();
+                    getCount = 0;
+                } else {
+                    getCount++;
+                }
+
+                StrongKey key = createStrongKey(mock);
+                InvocationHandlerAdapter adapter = adapters.get(key);
+                recycleStrongKey(key);
+
+                return adapter;
+            }
+        }
+
+        /**
+         * Remove entries that reference a stale mock from {@link #adapters}.
+         */
+        private void cleanStaleReferences() {
+            synchronized (lock) {
+                if (!isCleaning) {
+                    if (System.currentTimeMillis() - MIN_CLEAN_INTERVAL_MILLIS < mLastCleanup) {
+                        return;
+                    }
+
+                    isCleaning = true;
+
+                    AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
+                        @Override
+                        public void run() {
+                            synchronized (lock) {
+                                while (true) {
+                                    Reference<?> ref = MockMap.this.poll();
+                                    if (ref == null) {
+                                        break;
+                                    }
+
+                                    adapters.remove(ref);
+                                }
+
+                                mLastCleanup = System.currentTimeMillis();
+                                isCleaning = false;
+                            }
+                        }
+                    });
+                }
+            }
+        }
+
+        @Override
+        public InvocationHandlerAdapter put(Object mock, InvocationHandlerAdapter adapter) {
+            synchronized (lock) {
+                InvocationHandlerAdapter oldValue = remove(mock);
+                adapters.put(new WeakKey(mock), adapter);
+
+                return oldValue;
+            }
+        }
+
+        @Override
+        public InvocationHandlerAdapter remove(Object mock) {
+            synchronized (lock) {
+                StrongKey key = createStrongKey(mock);
+                InvocationHandlerAdapter adapter = adapters.remove(key);
+                recycleStrongKey(key);
+
+                return adapter;
+            }
+        }
+
+        @Override
+        public void putAll(Map<?, ? extends InvocationHandlerAdapter> map) {
+            synchronized (lock) {
+                for (Entry<?, ? extends InvocationHandlerAdapter> entry : map.entrySet()) {
+                    put(entry.getKey(), entry.getValue());
+                }
+            }
+        }
+
+        @Override
+        public void clear() {
+            synchronized (lock) {
+                adapters.clear();
+            }
+        }
+
+        @Override
+        public Set<Object> keySet() {
+            synchronized (lock) {
+                Set<Object> mocks = new ArraySet<>(adapters.size());
+
+                boolean hasStaleReferences = false;
+                for (WeakKey key : adapters.keySet()) {
+                    Object mock = key.get();
+
+                    if (mock == null) {
+                        hasStaleReferences = true;
+                    } else {
+                        mocks.add(mock);
+                    }
+                }
+
+                if (hasStaleReferences) {
+                    cleanStaleReferences();
+                }
+
+                return mocks;
+            }
+        }
+
+        @Override
+        public Collection<InvocationHandlerAdapter> values() {
+            synchronized (lock) {
+                return adapters.values();
+            }
+        }
+
+        @Override
+        public Set<Entry<Object, InvocationHandlerAdapter>> entrySet() {
+            synchronized (lock) {
+                Set<Entry<Object, InvocationHandlerAdapter>> entries = new ArraySet<>(
+                        adapters.size());
+
+                boolean hasStaleReferences = false;
+                for (Entry<WeakKey, InvocationHandlerAdapter> entry : adapters.entrySet()) {
+                    Object mock = entry.getKey().get();
+
+                    if (mock == null) {
+                        hasStaleReferences = true;
+                    } else {
+                        entries.add(new AbstractMap.SimpleEntry<>(mock, entry.getValue()));
+                    }
+                }
+
+                if (hasStaleReferences) {
+                    cleanStaleReferences();
+                }
+
+                return entries;
+            }
+        }
+
+        /**
+         * A weakly referencing wrapper to a mock.
+         *
+         * Only equals other weak or strong keys where the mock is the same.
+         */
+        private class WeakKey extends WeakReference<Object> {
+            private final int hashCode;
+
+            private WeakKey(/*@NonNull*/ Object obj) {
+                super(obj, MockMap.this);
+
+                // Cache the hashcode as the referenced object might disappear
+                hashCode = System.identityHashCode(obj);
+            }
+
+            @Override
+            public boolean equals(Object other) {
+                if (other == this) {
+                    return true;
+                }
+
+                if (other == null) {
+                    return false;
+                }
+
+                // Checking hashcode is cheap
+                if (other.hashCode() != hashCode) {
+                    return false;
+                }
+
+                Object obj = get();
+
+                if (obj == null) {
+                    cleanStaleReferences();
+                    return false;
+                }
+
+                if (other instanceof WeakKey) {
+                    Object otherObj = ((WeakKey) other).get();
+
+                    if (otherObj == null) {
+                        cleanStaleReferences();
+                        return false;
+                    }
+
+                    return obj == otherObj;
+                } else if (other instanceof StrongKey) {
+                    Object otherObj = ((StrongKey) other).obj;
+                    return obj == otherObj;
+                } else {
+                    return false;
+                }
+            }
+
+            @Override
+            public int hashCode() {
+                return hashCode;
+            }
+        }
+
+        /**
+         * A strongly referencing wrapper to a mock.
+         *
+         * Only equals other weak or strong keys where the mock is the same.
+         */
+        private class StrongKey {
+            /*@NonNull*/ private Object obj;
+
+            @Override
+            public boolean equals(Object other) {
+                if (other instanceof WeakKey) {
+                    Object otherObj = ((WeakKey) other).get();
+
+                    if (otherObj == null) {
+                        cleanStaleReferences();
+                        return false;
+                    }
+
+                    return obj == otherObj;
+                } else if (other instanceof StrongKey) {
+                    return this.obj == ((StrongKey)other).obj;
+                } else {
+                    return false;
+                }
+            }
+
+            @Override
+            public int hashCode() {
+                return System.identityHashCode(obj);
+            }
+        }
+    }
 }
diff --git a/dexmaker-mockito-inline/src/main/java/com/android/dx/mockito/inline/InterceptedInvocation.java b/dexmaker-mockito-inline/src/main/java/com/android/dx/mockito/inline/InterceptedInvocation.java
deleted file mode 100644
index a6d11b5..0000000
--- a/dexmaker-mockito-inline/src/main/java/com/android/dx/mockito/inline/InterceptedInvocation.java
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * Copyright (c) 2016 Mockito contributors
- * This program is made available under the terms of the MIT License.
- */
-
-package com.android.dx.mockito.inline;
-
-import org.mockito.internal.debugging.LocationImpl;
-import org.mockito.internal.exceptions.VerificationAwareInvocation;
-import org.mockito.internal.invocation.ArgumentsProcessor;
-import org.mockito.internal.invocation.MockitoMethod;
-import org.mockito.internal.reporting.PrintSettings;
-import org.mockito.invocation.Invocation;
-import org.mockito.invocation.Location;
-import org.mockito.invocation.StubInfo;
-
-import java.io.Serializable;
-import java.lang.reflect.Method;
-import java.util.Arrays;
-
-import static org.mockito.internal.exceptions.Reporter.cannotCallAbstractRealMethod;
-
-/**
- * {@link Invocation} used when intercepting methods from an method entry hook.
- */
-class InterceptedInvocation implements Invocation, VerificationAwareInvocation {
-    /** The mocked instance */
-    private final Object mock;
-
-    /** The method invoked */
-    private final MockitoMethod method;
-
-    /** expanded arguments to the method */
-    private final Object[] arguments;
-
-    /** raw arguments to the method */
-    private final Object[] rawArguments;
-
-    /** The super method */
-    private final SuperMethod superMethod;
-
-    /** sequence number of the invocation (different for each invocation) */
-    private final int sequenceNumber;
-
-    /** the location of the invocation (i.e. the stack trace) */
-    private final Location location;
-
-    /** Was this invocation {@link #markVerified() marked as verified} */
-    private boolean verified;
-
-    /** Should this be {@link #ignoreForVerification()} ignored for verification?} */
-    private boolean isIgnoredForVerification;
-
-    /** The stubinfo is this was {@link #markStubbed(StubInfo) markes as stubbed}*/
-    private StubInfo stubInfo;
-
-    /**
-     * Create a new invocation.
-     *
-     * @param mock mocked instance
-     * @param method method invoked
-     * @param arguments arguments to the method
-     * @param superMethod super method
-     * @param sequenceNumber sequence number of the invocation
-     */
-    InterceptedInvocation(Object mock, MockitoMethod method, Object[] arguments,
-                          SuperMethod superMethod, int sequenceNumber) {
-        this.mock = mock;
-        this.method = method;
-        this.arguments = ArgumentsProcessor.expandArgs(method, arguments);
-        this.rawArguments = arguments;
-        this.superMethod = superMethod;
-        this.sequenceNumber = sequenceNumber;
-        location = new LocationImpl();
-    }
-
-    @Override
-    public boolean isVerified() {
-        return verified || isIgnoredForVerification;
-    }
-
-    @Override
-    public int getSequenceNumber() {
-        return sequenceNumber;
-    }
-
-    @Override
-    public Location getLocation() {
-        return location;
-    }
-
-    @Override
-    public Object[] getRawArguments() {
-        return rawArguments;
-    }
-
-    @Override
-    public Class<?> getRawReturnType() {
-        return method.getReturnType();
-    }
-
-    @Override
-    public void markVerified() {
-        verified = true;
-    }
-
-    @Override
-    public StubInfo stubInfo() {
-        return stubInfo;
-    }
-
-    @Override
-    public void markStubbed(StubInfo stubInfo) {
-        this.stubInfo = stubInfo;
-    }
-
-    @Override
-    public boolean isIgnoredForVerification() {
-        return isIgnoredForVerification;
-    }
-
-    @Override
-    public void ignoreForVerification() {
-        isIgnoredForVerification = true;
-    }
-
-    @Override
-    public Object getMock() {
-        return mock;
-    }
-
-    @Override
-    public Method getMethod() {
-        return method.getJavaMethod();
-    }
-
-    @Override
-    public Object[] getArguments() {
-        return arguments;
-    }
-
-    @Override
-    @SuppressWarnings("unchecked")
-    public <T> T getArgument(int index) {
-        return (T) arguments[index];
-    }
-
-    @Override
-    public Object callRealMethod() throws Throwable {
-        if (!superMethod.isInvokable()) {
-            throw cannotCallAbstractRealMethod();
-        }
-        return superMethod.invoke();
-    }
-
-    @Override
-    public int hashCode() {
-        // TODO SF we need to provide hash code implementation so that there are no unexpected,
-        //         slight perf issues
-        return 1;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (o == null || !o.getClass().equals(this.getClass())) {
-            return false;
-        }
-        InterceptedInvocation other = (InterceptedInvocation) o;
-        return this.mock.equals(other.mock)
-                && this.method.equals(other.method)
-                && this.equalArguments(other.arguments);
-    }
-
-    private boolean equalArguments(Object[] arguments) {
-        return Arrays.equals(arguments, this.arguments);
-    }
-
-    @Override
-    public String toString() {
-        return new PrintSettings().print(ArgumentsProcessor.argumentsToMatchers(getArguments()),
-                this);
-    }
-
-    interface SuperMethod extends Serializable {
-        boolean isInvokable();
-
-        Object invoke() throws Throwable;
-    }
-}
diff --git a/dexmaker-mockito-inline/src/main/java/com/android/dx/mockito/inline/InvocationHandlerAdapter.java b/dexmaker-mockito-inline/src/main/java/com/android/dx/mockito/inline/InvocationHandlerAdapter.java
index 5d01a1e..afeedb2 100644
--- a/dexmaker-mockito-inline/src/main/java/com/android/dx/mockito/inline/InvocationHandlerAdapter.java
+++ b/dexmaker-mockito-inline/src/main/java/com/android/dx/mockito/inline/InvocationHandlerAdapter.java
@@ -16,22 +16,17 @@
 
 package com.android.dx.mockito.inline;
 
-import org.mockito.internal.creation.DelegatingMethod;
-import org.mockito.internal.debugging.LocationImpl;
-import org.mockito.internal.exceptions.VerificationAwareInvocation;
-import org.mockito.internal.invocation.ArgumentsProcessor;
-import org.mockito.internal.progress.SequenceNumber;
-import org.mockito.invocation.Invocation;
-import org.mockito.invocation.Location;
+import com.android.dx.stock.ProxyBuilder;
+
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationFactory.RealMethodBehavior;
 import org.mockito.invocation.MockHandler;
-import org.mockito.invocation.StubInfo;
 import org.mockito.mock.MockCreationSettings;
 
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
 
-import static org.mockito.internal.exceptions.Reporter.cannotCallAbstractRealMethod;
+import static org.mockito.Mockito.withSettings;
 
 /**
  * Handles proxy and entry hook method invocations added by
@@ -63,46 +58,65 @@
      *
      * @param mock mocked object
      * @param method method that was called
-     * @param args arguments to the method
+     * @param rawArgs arguments to the method
      * @param superMethod The super method
      *
      * @return mocked result
      * @throws Throwable An exception if thrown
      */
-    Object interceptEntryHook(Object mock, Method method, Object[] args,
-                              InterceptedInvocation.SuperMethod superMethod) throws Throwable {
-        return handler.handle(new InterceptedInvocation(mock, new DelegatingMethod(method), args,
-                superMethod, SequenceNumber.next()));
+    Object interceptEntryHook(final Object mock, final Method method, final Object[] rawArgs,
+                              final SuperMethod superMethod) throws Throwable {
+        // args can be null if the method invoked has no arguments, but Mockito expects a non-null
+        Object[] args = rawArgs;
+        if (rawArgs == null) {
+            args = new Object[0];
+        }
+
+        return handler.handle(Mockito.framework().getInvocationFactory().createInvocation(mock,
+                withSettings().build(mock.getClass()), method, new RealMethodBehavior() {
+                    @Override
+                    public Object call() throws Throwable {
+                        return superMethod.invoke();
+                    }
+                }, args));
     }
 
     /**
      * Intercept a method call. Called <u>before</u> a method is called by the proxied method.
      *
-     * <p>This does the same as {@link #interceptEntryHook(Object, Method, Object[],
-     * InterceptedInvocation.SuperMethod)} but this handles proxied methods. We only proxy abstract
-     * methods.
+     * <p>This does the same as {@link #interceptEntryHook(Object, Method, Object[], SuperMethod)}
+     * but this handles proxied methods. We only proxy abstract methods.
      *
      * @param proxy proxies object
      * @param method method that was called
-     * @param argsIn arguments to the method
+     * @param rawArgs arguments to the method
      *
      * @return mocked result
      * @throws Throwable An exception if thrown
      */
     @Override
-    public Object invoke(final Object proxy, final Method method, Object[] argsIn) throws
+    public Object invoke(final Object proxy, final Method method, final Object[] rawArgs) throws
             Throwable {
         // args can be null if the method invoked has no arguments, but Mockito expects a non-null
-        // array
-        final Object[] args = argsIn != null ? argsIn : new Object[0];
+        Object[] args = rawArgs;
+        if (rawArgs == null) {
+            args = new Object[0];
+        }
+
         if (isEqualsMethod(method)) {
             return proxy == args[0];
         } else if (isHashCodeMethod(method)) {
             return System.identityHashCode(proxy);
         }
 
-        return handler.handle(new ProxyInvocation(proxy, method, args, new DelegatingMethod
-                (method), SequenceNumber.next(), new LocationImpl()));
+        return handler.handle(Mockito.framework().getInvocationFactory().createInvocation(proxy,
+                withSettings().build(proxy.getClass().getSuperclass()), method,
+                new RealMethodBehavior() {
+                    @Override
+                    public Object call() throws Throwable {
+                        return ProxyBuilder.callSuper(proxy, method, rawArgs);
+                    }
+                }, args));
     }
 
     /**
@@ -122,106 +136,9 @@
     }
 
     /**
-     * Invocation on a proxy
+     * Interface used to describe a supermethod that can be called.
      */
-    private class ProxyInvocation implements Invocation, VerificationAwareInvocation {
-        private final Object proxy;
-        private final Method method;
-        private final Object[] rawArgs;
-        private final int sequenceNumber;
-        private final Location location;
-        private final Object[] args;
-
-        private StubInfo stubInfo;
-        private boolean isIgnoredForVerification;
-        private boolean verified;
-
-        private ProxyInvocation(Object proxy, Method method, Object[] rawArgs, DelegatingMethod
-                                mockitoMethod, int sequenceNumber, Location location) {
-            this.rawArgs = rawArgs;
-            this.proxy = proxy;
-            this.method = method;
-            this.sequenceNumber = sequenceNumber;
-            this.location = location;
-            args = ArgumentsProcessor.expandArgs(mockitoMethod, rawArgs);
-        }
-
-        @Override
-        public Object getMock() {
-            return proxy;
-        }
-
-        @Override
-        public Method getMethod() {
-            return method;
-        }
-
-        @Override
-        public Object[] getArguments() {
-            return args;
-        }
-
-        @Override
-        public <T> T getArgument(int index) {
-            return (T)args[index];
-        }
-
-        @Override
-        public Object callRealMethod() throws Throwable {
-            if (Modifier.isAbstract(method.getModifiers())) {
-                throw cannotCallAbstractRealMethod();
-            }
-            return method.invoke(proxy, rawArgs);
-        }
-
-        @Override
-        public boolean isVerified() {
-            return verified || isIgnoredForVerification;
-        }
-
-        @Override
-        public int getSequenceNumber() {
-            return sequenceNumber;
-        }
-
-        @Override
-        public Location getLocation() {
-            return location;
-        }
-
-        @Override
-        public Object[] getRawArguments() {
-            return rawArgs;
-        }
-
-        @Override
-        public Class<?> getRawReturnType() {
-            return method.getReturnType();
-        }
-
-        @Override
-        public void markVerified() {
-            verified = true;
-        }
-
-        @Override
-        public StubInfo stubInfo() {
-            return stubInfo;
-        }
-
-        @Override
-        public void markStubbed(StubInfo stubInfo) {
-            this.stubInfo = stubInfo;
-        }
-
-        @Override
-        public boolean isIgnoredForVerification() {
-            return isIgnoredForVerification;
-        }
-
-        @Override
-        public void ignoreForVerification() {
-            isIgnoredForVerification = true;
-        }
+    interface SuperMethod {
+        Object invoke() throws Throwable;
     }
 }
diff --git a/dexmaker-mockito-inline/src/main/java/com/android/dx/mockito/inline/JvmtiAgent.java b/dexmaker-mockito-inline/src/main/java/com/android/dx/mockito/inline/JvmtiAgent.java
index aca7a0c..34c5c48 100644
--- a/dexmaker-mockito-inline/src/main/java/com/android/dx/mockito/inline/JvmtiAgent.java
+++ b/dexmaker-mockito-inline/src/main/java/com/android/dx/mockito/inline/JvmtiAgent.java
@@ -17,14 +17,13 @@
 package com.android.dx.mockito.inline;
 
 import android.os.Build;
+import android.os.Debug;
 
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.lang.reflect.InvocationTargetException;
 import java.security.ProtectionDomain;
 import java.util.ArrayList;
 
@@ -52,42 +51,17 @@
      * @throws IOException If jvmti could not be enabled or agent could not be loaded
      */
     JvmtiAgent() throws IOException {
-        // TODO (moltmann@google.com): Replace with proper check for >= P
-        if (!Build.VERSION.CODENAME.equals("P")) {
-            throw new IOException("Requires Android P. Build is " + Build.VERSION.CODENAME);
+        if (Build.VERSION.SDK_INT < 28) {
+            throw new IOException("Requires API 28. API is " + Build.VERSION.SDK_INT);
         }
 
-        Throwable loadJvmtiException = null;
-
         ClassLoader cl = JvmtiAgent.class.getClassLoader();
         if (!(cl instanceof BaseDexClassLoader)) {
             throw new IOException("Could not load jvmti plugin as JvmtiAgent class was not loaded "
                     + "by a BaseDexClassLoader");
         }
 
-        try {
-            /*
-             * TODO (moltmann@google.com): Replace with regular method call once the API becomes
-             *                             public
-             */
-            Class.forName("android.os.Debug").getMethod("attachJvmtiAgent", String.class,
-                    String.class, ClassLoader.class).invoke(null, AGENT_LIB_NAME,
-                    null, cl);
-        } catch (InvocationTargetException e) {
-            loadJvmtiException = e.getCause();
-        } catch (IllegalAccessException | ClassNotFoundException | NoSuchMethodException e) {
-            loadJvmtiException = e;
-        }
-
-        if (loadJvmtiException != null) {
-            if (loadJvmtiException instanceof IOException) {
-                throw new IOException(cl.toString(), loadJvmtiException);
-            } else {
-                throw new IOException("Could not load jvmti plugin",
-                        loadJvmtiException);
-            }
-        }
-
+        Debug.attachJvmtiAgent(AGENT_LIB_NAME, null, cl);
         nativeRegisterTransformerHook();
     }
 
@@ -145,6 +119,18 @@
         }
     }
 
+    // called by JNI
+    @SuppressWarnings("unused")
+    public boolean shouldTransform(Class<?> classBeingRedefined) {
+        for (ClassTransformer transformer : transformers) {
+            if (transformer.shouldTransform(classBeingRedefined)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
     /**
      * Register a transformer. These are called for each class when a transformation was triggered
      * via {@link #requestTransformClasses(Class[])}.
diff --git a/dexmaker-mockito-inline/src/main/java/com/android/dx/mockito/inline/MockMethodAdvice.java b/dexmaker-mockito-inline/src/main/java/com/android/dx/mockito/inline/MockMethodAdvice.java
index 4cf2ac8..dfe242f 100644
--- a/dexmaker-mockito-inline/src/main/java/com/android/dx/mockito/inline/MockMethodAdvice.java
+++ b/dexmaker-mockito-inline/src/main/java/com/android/dx/mockito/inline/MockMethodAdvice.java
@@ -5,13 +5,11 @@
 
 package com.android.dx.mockito.inline;
 
-import org.mockito.internal.exceptions.stacktrace.ConditionalStackTraceFilter;
-import org.mockito.internal.util.concurrent.WeakConcurrentMap;
-
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.util.ArrayList;
+import java.util.Map;
 import java.util.concurrent.Callable;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -21,14 +19,14 @@
  * be ignored.
  */
 class MockMethodAdvice {
-    private final WeakConcurrentMap<Object, InvocationHandlerAdapter> interceptors;
+    private final Map<Object, InvocationHandlerAdapter> interceptors;
 
     /** Pattern to decompose a instrumentedMethodWithTypeAndSignature */
     private final Pattern methodPattern = Pattern.compile("(.*)#(.*)\\((.*)\\)");
 
     private final SelfCallInfo selfCallInfo = new SelfCallInfo();
 
-    MockMethodAdvice(WeakConcurrentMap<Object, InvocationHandlerAdapter> interceptors) {
+    MockMethodAdvice(Map<Object, InvocationHandlerAdapter> interceptors) {
         this.interceptors = interceptors;
     }
 
@@ -48,12 +46,7 @@
         try {
             return origin.invoke(instance, arguments);
         } catch (InvocationTargetException exception) {
-            Throwable cause = exception.getCause();
-
-            new ConditionalStackTraceFilter().filter(hideRecursiveCall(cause,
-                    new Throwable().getStackTrace().length, origin.getDeclaringClass()));
-
-            throw cause;
+            throw exception.getCause();
         }
     }
 
@@ -259,7 +252,7 @@
     /**
      * Used to call the read (non mocked) method.
      */
-    private static class SuperMethodCall implements InterceptedInvocation.SuperMethod {
+    private static class SuperMethodCall implements InvocationHandlerAdapter.SuperMethod {
         private final SelfCallInfo selfCallInfo;
         private final Method origin;
         private final Object instance;
@@ -273,11 +266,6 @@
             this.arguments = arguments;
         }
 
-        @Override
-        public boolean isInvokable() {
-            return true;
-        }
-
         /**
          * Call the read (non mocked) method.
          *
diff --git a/dexmaker-mockito-inline/src/main/jni/dexmakerjvmtiagent/agent.cc b/dexmaker-mockito-inline/src/main/jni/dexmakerjvmtiagent/agent.cc
index e00ada8..cfa10a5 100644
--- a/dexmaker-mockito-inline/src/main/jni/dexmakerjvmtiagent/agent.cc
+++ b/dexmaker-mockito-inline/src/main/jni/dexmakerjvmtiagent/agent.cc
@@ -25,13 +25,13 @@
 
 #include "jvmti.h"
 
-#include <dex_ir.h>
-#include <code_ir.h>
-#include <dex_ir_builder.h>
-#include <dex_utf8.h>
-#include <writer.h>
-#include <reader.h>
-#include <instrumentation.h>
+#include <slicer/dex_ir.h>
+#include <slicer/code_ir.h>
+#include <slicer/dex_ir_builder.h>
+#include <slicer/dex_utf8.h>
+#include <slicer/writer.h>
+#include <slicer/reader.h>
+#include <slicer/instrumentation.h>
 
 using namespace dex;
 using namespace lir;
@@ -70,6 +70,19 @@
           jint* newClassDataLen,
           unsigned char** newClassData) {
     if (sTransformer != NULL) {
+        // Even reading the classData array is expensive as the data is only generated when the
+        // memory is touched. Hence call JvmtiAgent#shouldTransform to check if we need to transform
+        // the class.
+        jclass cls = env->GetObjectClass(sTransformer);
+        jmethodID shouldTransformMethod = env->GetMethodID(cls, "shouldTransform",
+                                                           "(Ljava/lang/Class;)Z");
+
+        jboolean shouldTransform = env->CallBooleanMethod(sTransformer, shouldTransformMethod,
+                                                          classBeingRedefined);
+        if (!shouldTransform) {
+            return;
+        }
+
         // Isolate byte code of class class. This is needed as Android usually gives us more
         // than the class we need.
         Reader reader(classData, classDataLen);
@@ -97,16 +110,15 @@
         jstring nameStr = env->NewStringUTF(name);
 
         // Call JvmtiAgent#runTransformers
-        jclass cls = env->GetObjectClass(sTransformer);
-        jmethodID runTransformers = env->GetMethodID(cls, "runTransformers",
-                                                     "(Ljava/lang/ClassLoader;"
-                                                     "Ljava/lang/String;"
-                                                     "Ljava/lang/Class;"
-                                                     "Ljava/security/ProtectionDomain;"
-                                                     "[B)[B");
+        jmethodID runTransformersMethod = env->GetMethodID(cls, "runTransformers",
+                                                           "(Ljava/lang/ClassLoader;"
+                                                           "Ljava/lang/String;"
+                                                           "Ljava/lang/Class;"
+                                                           "Ljava/security/ProtectionDomain;"
+                                                           "[B)[B");
 
         jbyteArray transformedArr = (jbyteArray) env->CallObjectMethod(sTransformer,
-                                                                       runTransformers,
+                                                                       runTransformersMethod,
                                                                        loader, nameStr,
                                                                        classBeingRedefined,
                                                                        protectionDomain,
diff --git a/dexmaker-mockito-tests/build.gradle b/dexmaker-mockito-tests/build.gradle
index f0befe0..a08e254 100644
--- a/dexmaker-mockito-tests/build.gradle
+++ b/dexmaker-mockito-tests/build.gradle
@@ -20,6 +20,7 @@
 
 repositories {
     jcenter()
+    google()
 }
 
 dependencies {
diff --git a/dexmaker-mockito-tests/src/androidTest/java/com/android/dx/mockito/tests/BlacklistedApis.java b/dexmaker-mockito-tests/src/androidTest/java/com/android/dx/mockito/tests/BlacklistedApis.java
new file mode 100644
index 0000000..ffe55fb
--- /dev/null
+++ b/dexmaker-mockito-tests/src/androidTest/java/com/android/dx/mockito/tests/BlacklistedApis.java
@@ -0,0 +1,161 @@
+/*
+ * 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.dx.mockito.tests;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.provider.Settings;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Simplified versions of bugs seen in the past
+ */
+@RunWith(AndroidJUnit4.class)
+public class BlacklistedApis {
+    /**
+     * Check if the application is marked as {@code android:debuggable} in the manifest
+     *
+     * @return {@code true} iff it is marked as such
+     */
+    private boolean isDebuggable() throws PackageManager.NameNotFoundException {
+        Context context = InstrumentationRegistry.getTargetContext();
+        PackageInfo packageInfo = context.getPackageManager().getPackageInfo(
+                context.getPackageName(), 0);
+
+        return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
+    }
+
+    @Test
+    public void callBlacklistedPublicMethodRealMethod() throws Exception {
+        Context targetContext = InstrumentationRegistry.getTargetContext();
+
+        FrameLayout child = new FrameLayout(targetContext);
+        FrameLayout parent = spy(new FrameLayout(targetContext));
+
+        if (isDebuggable()) {
+            // This calls a blacklisted public method.
+            // Since Android P these methods are not callable from outside of the Android framework
+            // anymore:
+            //
+            // https://android-developers.googleblog.com/2018/02/
+            // improving-stability-by-reducing-usage.html
+            //
+            // Hence if we use a subclass mock this will fail. Inline mocking does not have this
+            // problem as the mock class is the same as the mocked class.
+            parent.addView(child);
+        } else {
+            try {
+                parent.addView(child);
+                fail();
+            } catch (NoSuchMethodError expected) {
+                // expected
+            }
+        }
+    }
+
+    @Test
+    public void copyBlacklistedFields() throws Exception {
+        assumeTrue(isDebuggable());
+
+        Context targetContext = InstrumentationRegistry.getTargetContext();
+
+        FrameLayout child = new FrameLayout(targetContext);
+        FrameLayout parent = spy(new FrameLayout(targetContext));
+
+        parent.addView(child);
+
+        // During cloning of the parent spy, all fields are copied. This accesses a blacklisted
+        // fields. Since Android P these fields are not visible from outside of the Android
+        // framework anymore:
+        //
+        // https://android-developers.googleblog.com/2018/02/
+        // improving-stability-by-reducing-usage.html
+        //
+        // As 'measure' requires the fields to be initialized, this fails if the fields are not
+        // copied.
+        parent.measure(100, 100);
+    }
+
+    @Test
+    public void cannotCallBlackListedAfterSpying() {
+        // Spying and mocking might change the View class's byte code
+        spy(new View(InstrumentationRegistry.getTargetContext(), null));
+        mock(View.class);
+
+        // View#setNotifyAutofillManagerOnClick is a blacklisted method. Resolving it should fail
+        try {
+            View.class.getDeclaredMethod("setNotifyAutofillManagerOnClick", Boolean.TYPE);
+            fail();
+        } catch (NoSuchMethodException expected) {
+            // expected
+        }
+    }
+
+    public class CallBlackListedMethod {
+        public boolean callingBlacklistedMethodCausesException() {
+            // Settings.Global#isValidZenMode is a blacklisted method. Resolving it should fail
+            try {
+                Settings.Global.class.getDeclaredMethod("isValidZenMode", Integer.TYPE);
+                return false;
+            } catch (NoSuchMethodException expected) {
+                return true;
+            }
+        }
+    }
+
+    @Test
+    public void spiesCannotBeUsedToCallHiddenMethods() {
+        CallBlackListedMethod t = spy(new CallBlackListedMethod());
+        assertTrue(t.callingBlacklistedMethodCausesException());
+    }
+
+    public abstract class CallBlacklistedMethodAbstract {
+        public boolean callingBlacklistedMethodCausesException() {
+            // Settings.Global#isValidZenMode is a blacklisted method. Resolving it should fail
+            try {
+                Settings.Global.class.getDeclaredMethod("isValidZenMode", Integer.TYPE);
+                return false;
+            } catch (NoSuchMethodException expected) {
+                return true;
+            }
+        }
+
+        public abstract void unused();
+    }
+
+    @Test
+    public void mocksOfAbstractClassesCannotBeUsedToCallHiddenMethods() {
+        CallBlacklistedMethodAbstract t = mock(CallBlacklistedMethodAbstract.class);
+        doCallRealMethod().when(t).callingBlacklistedMethodCausesException();
+        assertTrue(t.callingBlacklistedMethodCausesException());
+    }
+}
diff --git a/dexmaker-mockito-tests/src/androidTest/java/com/android/dx/mockito/tests/GeneralMocking.java b/dexmaker-mockito-tests/src/androidTest/java/com/android/dx/mockito/tests/GeneralMocking.java
index 6f824f2..36d5612 100644
--- a/dexmaker-mockito-tests/src/androidTest/java/com/android/dx/mockito/tests/GeneralMocking.java
+++ b/dexmaker-mockito-tests/src/androidTest/java/com/android/dx/mockito/tests/GeneralMocking.java
@@ -18,13 +18,24 @@
 
 import android.support.test.runner.AndroidJUnit4;
 
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.exceptions.base.MockitoException;
 import org.mockito.exceptions.verification.NoInteractionsWanted;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -36,10 +47,32 @@
         public String returnA() {
             return "A";
         }
+
+        public String throwThrowable() throws Throwable {
+            throw new Throwable();
+        }
+
+        public String throwOutOfMemoryError() throws OutOfMemoryError {
+            throw new OutOfMemoryError();
+        }
+
+        public void throwNullPointerException() {
+            throw new NullPointerException();
+        }
+
+        public String concat(String a, String b) {
+            return a + b;
+        }
+    }
+
+    public static class TestSubClass extends TestClass {
+
     }
 
     public interface TestInterface {
         String returnA();
+
+        String concat(String a, String b);
     }
 
     @Test
@@ -103,7 +136,147 @@
                 assertTrue(e.getMessage(),
                         e.getMessage().contains(here.getStackTrace()[0].getMethodName()));
             }
-
         }
     }
+
+    @Test
+    public void spyThrowingMethod() throws Exception {
+        TestClass t = spy(TestClass.class);
+
+        try {
+            t.throwThrowable();
+        } catch (Throwable e) {
+            assertEquals("throwThrowable", e.getStackTrace()[0].getMethodName());
+            return;
+        }
+
+        fail();
+    }
+
+    @Test()
+    public void spyErrorMethod() throws Exception {
+        TestClass t = spy(TestClass.class);
+
+        try {
+            t.throwOutOfMemoryError();
+            fail();
+        } catch (OutOfMemoryError e) {
+            assertEquals("throwOutOfMemoryError", e.getStackTrace()[0].getMethodName());
+        }
+    }
+
+    @Test()
+    public void spyExceptingMethod() throws Exception {
+        TestClass t = spy(TestClass.class);
+
+        try {
+            t.throwNullPointerException();
+            fail();
+        } catch (NullPointerException e) {
+            assertEquals("throwNullPointerException", e.getStackTrace()[0].getMethodName());
+        }
+    }
+
+
+    @Test
+    public void callAbstractRealMethod() throws Exception {
+        TestInterface t = mock(TestInterface.class);
+
+        try {
+            when(t.returnA()).thenCallRealMethod();
+            fail();
+        } catch (MockitoException e) {
+            assertEquals("callAbstractRealMethod", e.getStackTrace()[0].getMethodName());
+        }
+    }
+
+    @Test
+    public void callInterfaceWithoutMatcher() throws Exception {
+        TestInterface t = mock(TestInterface.class);
+
+        when(t.concat("a", "b")).thenReturn("match");
+
+        assertEquals("match", t.concat("a", "b"));
+        assertNull(t.concat("b", "a"));
+    }
+
+    @Test
+    public void callInterfaceWithMatcher() throws Exception {
+        TestInterface t = mock(TestInterface.class);
+
+        when(t.concat(eq("a"), anyString())).thenReturn("match");
+
+        assertEquals("match", t.concat("a", "b"));
+        assertNull(t.concat("b", "a"));
+    }
+
+    @Test
+    public void callInterfaceWithNullMatcher() throws Exception {
+        TestInterface t = mock(TestInterface.class);
+
+        when(t.concat(eq("a"), (String) isNull())).thenReturn("match");
+
+        assertEquals("match", t.concat("a", null));
+        assertNull(t.concat("a", "b"));
+    }
+
+    @Test
+    public void callClassWithoutMatcher() throws Exception {
+        TestClass t = spy(TestClass.class);
+
+        when(t.concat("a", "b")).thenReturn("match");
+
+        assertEquals("match", t.concat("a", "b"));
+        assertEquals("ba", t.concat("b", "a"));
+    }
+
+    @Test
+    public void callClassWithMatcher() throws Exception {
+        TestClass t = spy(TestClass.class);
+
+        when(t.concat(eq("a"), anyString())).thenReturn("match");
+
+        assertEquals("match", t.concat("a", "b"));
+        assertEquals("ba", t.concat("b", "a"));
+    }
+
+    @Test
+    public void callClassWithNullMatcher() throws Exception {
+        TestClass t = spy(TestClass.class);
+
+        when(t.concat(eq("a"), (String) isNull())).thenReturn("match");
+
+        assertEquals("match", t.concat("a", null));
+        assertEquals("ab", t.concat("a", "b"));
+    }
+
+    @Test
+    public void callSubClassWithoutMatcher() throws Exception {
+        TestSubClass t = spy(TestSubClass.class);
+
+        when(t.concat("a", "b")).thenReturn("match");
+
+        assertEquals("match", t.concat("a", "b"));
+        assertEquals("ba", t.concat("b", "a"));
+    }
+
+    @Test
+    public void callSubClassWithMatcher() throws Exception {
+        TestSubClass t = spy(TestSubClass.class);
+
+        when(t.concat(eq("a"), anyString())).thenReturn("match");
+
+        assertEquals("match", t.concat("a", "b"));
+        assertEquals("ba", t.concat("b", "a"));
+    }
+
+    @Test
+    public void callSubClassWithNullMatcher() throws Exception {
+        TestSubClass t = spy(TestSubClass.class);
+
+        when(t.concat(eq("a"), (String) isNull())).thenReturn("match");
+
+        assertEquals("match", t.concat("a", null));
+        assertEquals("ab", t.concat("a", "b"));
+    }
 }
diff --git a/dexmaker-mockito/build.gradle b/dexmaker-mockito/build.gradle
index e1f5133..96479c5 100644
--- a/dexmaker-mockito/build.gradle
+++ b/dexmaker-mockito/build.gradle
@@ -14,5 +14,5 @@
 dependencies {
     compile project(":dexmaker")
 
-    compile 'org.mockito:mockito-core:2.12.0'
+    compile 'org.mockito:mockito-core:2.15.0', { exclude group: "net.bytebuddy" }
 }
diff --git a/dexmaker-mockito/src/main/java/com/android/dx/mockito/DexmakerMockMaker.java b/dexmaker-mockito/src/main/java/com/android/dx/mockito/DexmakerMockMaker.java
index b19f7bd..19f371e 100644
--- a/dexmaker-mockito/src/main/java/com/android/dx/mockito/DexmakerMockMaker.java
+++ b/dexmaker-mockito/src/main/java/com/android/dx/mockito/DexmakerMockMaker.java
@@ -16,15 +16,21 @@
 
 package com.android.dx.mockito;
 
+import android.os.Build;
+import android.util.Log;
+
 import com.android.dx.stock.ProxyBuilder;
 import org.mockito.exceptions.base.MockitoException;
 import org.mockito.exceptions.stacktrace.StackTraceCleaner;
+import org.mockito.internal.util.reflection.LenientCopyTool;
 import org.mockito.invocation.MockHandler;
 import org.mockito.mock.MockCreationSettings;
 import org.mockito.plugins.MockMaker;
 import org.mockito.plugins.StackTraceCleanerProvider;
 
 import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.lang.reflect.Proxy;
 import java.util.Set;
@@ -33,8 +39,42 @@
  * Generates mock instances on Android's runtime.
  */
 public final class DexmakerMockMaker implements MockMaker, StackTraceCleanerProvider {
+    private static final String LOG_TAG = DexmakerMockMaker.class.getSimpleName();
+
     private final UnsafeAllocator unsafeAllocator = UnsafeAllocator.create();
 
+    public DexmakerMockMaker() {
+        if (Build.VERSION.SDK_INT >= 28) {
+            // Blacklisted APIs were introduced in Android P:
+            //
+            // https://android-developers.googleblog.com/2018/02/
+            // improving-stability-by-reducing-usage.html
+            //
+            // This feature prevents access to blacklisted fields and calling of blacklisted APIs
+            // if the calling class is not trusted.
+            Method allowHiddenApiReflectionFromMethod;
+            try {
+                Class vmDebug = Class.forName("dalvik.system.VMDebug");
+                allowHiddenApiReflectionFromMethod = vmDebug.getDeclaredMethod(
+                        "allowHiddenApiReflectionFrom", Class.class);
+            } catch (ClassNotFoundException | NoSuchMethodException e) {
+                throw new IllegalStateException(
+                        "Cannot find VMDebug#allowHiddenApiReflectionFrom. Method is needed to "
+                                + "allow spies to copy blacklisted fields.");
+            }
+
+            // The LenientCopyTool copies the fields to a spy when creating the copy from an
+            // existing object. Some of the fields might be blacklisted. Marking the LenientCopyTool
+            // as trusted allows the tool to copy all fields, including the blacklisted ones.
+            try {
+                allowHiddenApiReflectionFromMethod.invoke(null, LenientCopyTool.class);
+            } catch (InvocationTargetException | IllegalAccessException e) {
+                Log.w(LOG_TAG, "Cannot allow LenientCopyTool to copy spies of blacklisted fields. "
+                        + "This might break spying on system classes.");
+            }
+        }
+    }
+
     @Override
     public <T> T createMock(MockCreationSettings<T> settings, MockHandler handler) {
         Class<T> typeToMock = settings.getTypeToMock();
diff --git a/dexmaker-mockito/src/main/java/com/android/dx/mockito/InterceptedInvocation.java b/dexmaker-mockito/src/main/java/com/android/dx/mockito/InterceptedInvocation.java
deleted file mode 100644
index b0b42e9..0000000
--- a/dexmaker-mockito/src/main/java/com/android/dx/mockito/InterceptedInvocation.java
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * Copyright (c) 2016 Mockito contributors
- * This program is made available under the terms of the MIT License.
- */
-
-package com.android.dx.mockito;
-
-import org.mockito.internal.debugging.LocationImpl;
-import org.mockito.internal.exceptions.VerificationAwareInvocation;
-import org.mockito.internal.invocation.ArgumentsProcessor;
-import org.mockito.internal.invocation.MockitoMethod;
-import org.mockito.internal.reporting.PrintSettings;
-import org.mockito.invocation.Invocation;
-import org.mockito.invocation.Location;
-import org.mockito.invocation.StubInfo;
-
-import java.io.Serializable;
-import java.lang.reflect.Method;
-import java.util.Arrays;
-
-import static org.mockito.internal.exceptions.Reporter.cannotCallAbstractRealMethod;
-
-/**
- * {@link Invocation} used when intercepting methods from an method entry hook.
- */
-class InterceptedInvocation implements Invocation, VerificationAwareInvocation {
-    /** The mocked instance */
-    private final Object mock;
-
-    /** The method invoked */
-    private final MockitoMethod method;
-
-    /** expanded arguments to the method */
-    private final Object[] arguments;
-
-    /** raw arguments to the method */
-    private final Object[] rawArguments;
-
-    /** The super method */
-    private final SuperMethod superMethod;
-
-    /** sequence number of the invocation (different for each invocation) */
-    private final int sequenceNumber;
-
-    /** the location of the invocation (i.e. the stack trace) */
-    private final Location location;
-
-    /** Was this invocation {@link #markVerified() marked as verified} */
-    private boolean verified;
-
-    /** Should this be {@link #ignoreForVerification()} ignored for verification?} */
-    private boolean isIgnoredForVerification;
-
-    /** The stubinfo is this was {@link #markStubbed(StubInfo) markes as stubbed}*/
-    private StubInfo stubInfo;
-
-    /**
-     * Create a new invocation.
-     *
-     * @param mock mocked instance
-     * @param method method invoked
-     * @param arguments arguments to the method
-     * @param superMethod super method
-     * @param sequenceNumber sequence number of the invocation
-     */
-    InterceptedInvocation(Object mock, MockitoMethod method, Object[] arguments,
-                          SuperMethod superMethod, int sequenceNumber) {
-        this.mock = mock;
-        this.method = method;
-        this.arguments = ArgumentsProcessor.expandArgs(method, arguments);
-        this.rawArguments = arguments;
-        this.superMethod = superMethod;
-        this.sequenceNumber = sequenceNumber;
-        location = new LocationImpl();
-    }
-
-    @Override
-    public boolean isVerified() {
-        return verified || isIgnoredForVerification;
-    }
-
-    @Override
-    public int getSequenceNumber() {
-        return sequenceNumber;
-    }
-
-    @Override
-    public Location getLocation() {
-        return location;
-    }
-
-    @Override
-    public Object[] getRawArguments() {
-        return rawArguments;
-    }
-
-    @Override
-    public Class<?> getRawReturnType() {
-        return method.getReturnType();
-    }
-
-    @Override
-    public void markVerified() {
-        verified = true;
-    }
-
-    @Override
-    public StubInfo stubInfo() {
-        return stubInfo;
-    }
-
-    @Override
-    public void markStubbed(StubInfo stubInfo) {
-        this.stubInfo = stubInfo;
-    }
-
-    @Override
-    public boolean isIgnoredForVerification() {
-        return isIgnoredForVerification;
-    }
-
-    @Override
-    public void ignoreForVerification() {
-        isIgnoredForVerification = true;
-    }
-
-    @Override
-    public Object getMock() {
-        return mock;
-    }
-
-    @Override
-    public Method getMethod() {
-        return method.getJavaMethod();
-    }
-
-    @Override
-    public Object[] getArguments() {
-        return arguments;
-    }
-
-    @Override
-    @SuppressWarnings("unchecked")
-    public <T> T getArgument(int index) {
-        return (T) arguments[index];
-    }
-
-    @Override
-    public Object callRealMethod() throws Throwable {
-        if (!superMethod.isInvokable()) {
-            throw cannotCallAbstractRealMethod();
-        }
-        return superMethod.invoke();
-    }
-
-    @Override
-    public int hashCode() {
-        // TODO SF we need to provide hash code implementation so that there are no unexpected,
-        //         slight perf issues
-        return 1;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (o == null || !o.getClass().equals(this.getClass())) {
-            return false;
-        }
-        InterceptedInvocation other = (InterceptedInvocation) o;
-        return this.mock.equals(other.mock)
-                && this.method.equals(other.method)
-                && this.equalArguments(other.arguments);
-    }
-
-    private boolean equalArguments(Object[] arguments) {
-        return Arrays.equals(arguments, this.arguments);
-    }
-
-    @Override
-    public String toString() {
-        return new PrintSettings().print(ArgumentsProcessor.argumentsToMatchers(getArguments()),
-                this);
-    }
-
-    interface SuperMethod extends Serializable {
-        boolean isInvokable();
-
-        Object invoke() throws Throwable;
-    }
-}
diff --git a/dexmaker-mockito/src/main/java/com/android/dx/mockito/InvocationHandlerAdapter.java b/dexmaker-mockito/src/main/java/com/android/dx/mockito/InvocationHandlerAdapter.java
index bab9265..4a95e05 100644
--- a/dexmaker-mockito/src/main/java/com/android/dx/mockito/InvocationHandlerAdapter.java
+++ b/dexmaker-mockito/src/main/java/com/android/dx/mockito/InvocationHandlerAdapter.java
@@ -18,21 +18,14 @@
 
 import com.android.dx.stock.ProxyBuilder;
 
-import org.mockito.internal.creation.DelegatingMethod;
-import org.mockito.internal.debugging.LocationImpl;
-import org.mockito.internal.exceptions.VerificationAwareInvocation;
-import org.mockito.internal.invocation.ArgumentsProcessor;
-import org.mockito.internal.progress.SequenceNumber;
-import org.mockito.invocation.Invocation;
-import org.mockito.invocation.Location;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationFactory.RealMethodBehavior;
 import org.mockito.invocation.MockHandler;
-import org.mockito.invocation.StubInfo;
 
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
 
-import static org.mockito.internal.exceptions.Reporter.cannotCallAbstractRealMethod;
+import static org.mockito.Mockito.withSettings;
 
 /**
  * Handles proxy method invocations to dexmaker's InvocationHandler by calling
@@ -46,17 +39,24 @@
     }
 
     @Override
-    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+    public Object invoke(final Object proxy, final Method method, final Object[] rawArgs)
+            throws Throwable {
         // args can be null if the method invoked has no arguments, but Mockito expects a non-null array
-        args = args != null ? args : new Object[0];
+        Object[] args = rawArgs != null ? rawArgs : new Object[0];
         if (isEqualsMethod(method)) {
             return proxy == args[0];
         } else if (isHashCodeMethod(method)) {
             return System.identityHashCode(proxy);
         }
 
-        return handler.handle(new ProxyInvocation(proxy, method, args, new DelegatingMethod
-                (method), SequenceNumber.next(), new LocationImpl()));
+        return handler.handle(Mockito.framework().getInvocationFactory().createInvocation(proxy,
+                withSettings().build(proxy.getClass().getSuperclass()), method,
+                new RealMethodBehavior() {
+            @Override
+            public Object call() throws Throwable {
+                return ProxyBuilder.callSuper(proxy, method, rawArgs);
+            }
+        }, args));
     }
 
     public MockHandler getHandler() {
@@ -77,108 +77,4 @@
         return method.getName().equals("hashCode")
                 && method.getParameterTypes().length == 0;
     }
-
-    /**
-     * Invocation on a proxy
-     */
-    private class ProxyInvocation implements Invocation, VerificationAwareInvocation {
-        private final Object proxy;
-        private final Method method;
-        private final Object[] rawArgs;
-        private final int sequenceNumber;
-        private final Location location;
-        private final Object[] args;
-
-        private StubInfo stubInfo;
-        private boolean isIgnoredForVerification;
-        private boolean verified;
-
-        private ProxyInvocation(Object proxy, Method method, Object[] rawArgs, DelegatingMethod
-                mockitoMethod, int sequenceNumber, Location location) {
-            this.rawArgs = rawArgs;
-            this.proxy = proxy;
-            this.method = method;
-            this.sequenceNumber = sequenceNumber;
-            this.location = location;
-            args = ArgumentsProcessor.expandArgs(mockitoMethod, rawArgs);
-        }
-
-        @Override
-        public Object getMock() {
-            return proxy;
-        }
-
-        @Override
-        public Method getMethod() {
-            return method;
-        }
-
-        @Override
-        public Object[] getArguments() {
-            return args;
-        }
-
-        @Override
-        public <T> T getArgument(int index) {
-            return (T)args[index];
-        }
-
-        @Override
-        public Object callRealMethod() throws Throwable {
-            if (Modifier.isAbstract(method.getModifiers())) {
-                throw cannotCallAbstractRealMethod();
-            }
-            return ProxyBuilder.callSuper(proxy, method, rawArgs);
-        }
-
-        @Override
-        public boolean isVerified() {
-            return verified || isIgnoredForVerification;
-        }
-
-        @Override
-        public int getSequenceNumber() {
-            return sequenceNumber;
-        }
-
-        @Override
-        public Location getLocation() {
-            return location;
-        }
-
-        @Override
-        public Object[] getRawArguments() {
-            return rawArgs;
-        }
-
-        @Override
-        public Class<?> getRawReturnType() {
-            return method.getReturnType();
-        }
-
-        @Override
-        public void markVerified() {
-            verified = true;
-        }
-
-        @Override
-        public StubInfo stubInfo() {
-            return stubInfo;
-        }
-
-        @Override
-        public void markStubbed(StubInfo stubInfo) {
-            this.stubInfo = stubInfo;
-        }
-
-        @Override
-        public boolean isIgnoredForVerification() {
-            return isIgnoredForVerification;
-        }
-
-        @Override
-        public void ignoreForVerification() {
-            isIgnoredForVerification = true;
-        }
-    }
 }
diff --git a/dexmaker-tests/src/androidTest/java/com/android/dx/AnnotationIdTest.java b/dexmaker-tests/src/androidTest/java/com/android/dx/AnnotationIdTest.java
new file mode 100644
index 0000000..43731ee
--- /dev/null
+++ b/dexmaker-tests/src/androidTest/java/com/android/dx/AnnotationIdTest.java
@@ -0,0 +1,400 @@
+/*
+ * 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.dx;
+
+import android.support.test.InstrumentationRegistry;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.lang.annotation.*;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static com.android.dx.TypeId.*;
+import static java.lang.reflect.Modifier.PUBLIC;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+public final class AnnotationIdTest {
+
+    /**
+     *  Method Annotation definition for test
+     */
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target({ElementType.METHOD})
+    @interface MethodAnnotation {
+        boolean elementBoolean() default false;
+        byte elementByte() default Byte.MIN_VALUE;
+        char elementChar() default 'a';
+        double elementDouble() default Double.MIN_NORMAL;
+        float elementFloat() default Float.MIN_NORMAL;
+        int elementInt() default Integer.MIN_VALUE;
+        long elementLong() default Long.MIN_VALUE;
+        short elementShort() default Short.MIN_VALUE;
+        String elementString() default "foo";
+        ElementEnum elementEnum() default ElementEnum.INSTANCE_0;
+        Class<?> elementClass() default Object.class;
+    }
+
+    enum ElementEnum {
+        INSTANCE_0,
+        INSTANCE_1,
+    }
+
+    private DexMaker dexMaker;
+    private static TypeId<?> GENERATED = TypeId.get("LGenerated;");
+    private static final Map<TypeId<?>, Class<?>> TYPE_TO_PRIMITIVE = new HashMap<>();
+    static {
+        TYPE_TO_PRIMITIVE.put(BOOLEAN, boolean.class);
+        TYPE_TO_PRIMITIVE.put(BYTE, byte.class);
+        TYPE_TO_PRIMITIVE.put(CHAR, char.class);
+        TYPE_TO_PRIMITIVE.put(DOUBLE, double.class);
+        TYPE_TO_PRIMITIVE.put(FLOAT, float.class);
+        TYPE_TO_PRIMITIVE.put(INT, int.class);
+        TYPE_TO_PRIMITIVE.put(LONG, long.class);
+        TYPE_TO_PRIMITIVE.put(SHORT, short.class);
+        TYPE_TO_PRIMITIVE.put(VOID, void.class);
+    }
+
+    @Before
+    public void setUp() {
+        init();
+    }
+
+    /**
+     *  Test adding a method annotation with new value of Boolean element.
+     */
+    @Test
+    public void addMethodAnnotationWithBooleanElement() throws Exception {
+        MethodId<?, Void> methodId = generateVoidMethod(TypeId.BOOLEAN);
+        AnnotationId.Element element = new AnnotationId.Element("elementBoolean", true);
+        addAnnotationToMethod(methodId, element);
+
+        Annotation[] methodAnnotations = getMethodAnnotations(methodId);
+        assertEquals(methodAnnotations.length, 1);
+
+        Boolean elementBoolean = ((MethodAnnotation)methodAnnotations[0]).elementBoolean();
+        assertEquals(true, elementBoolean);
+    }
+
+    /**
+     *  Test adding a method annotation with new value of Byte element.
+     */
+    @Test
+    public void addMethodAnnotationWithByteElement() throws Exception {
+        MethodId<?, Void> methodId = generateVoidMethod(TypeId.BYTE);
+        AnnotationId.Element element = new AnnotationId.Element("elementByte", Byte.MAX_VALUE);
+        addAnnotationToMethod(methodId, element);
+
+        Annotation[] methodAnnotations = getMethodAnnotations(methodId);
+        assertEquals(methodAnnotations.length, 1);
+
+        byte elementByte = ((MethodAnnotation)methodAnnotations[0]).elementByte();
+        assertEquals(Byte.MAX_VALUE, elementByte);
+    }
+
+    /**
+     *  Test adding a method annotation with new value of Char element.
+     */
+    @Test
+    public void addMethodAnnotationWithCharElement() throws Exception {
+        MethodId<?, Void> methodId = generateVoidMethod(TypeId.CHAR);
+        AnnotationId.Element element = new AnnotationId.Element("elementChar", 'X');
+        addAnnotationToMethod(methodId, element);
+
+        Annotation[] methodAnnotations = getMethodAnnotations(methodId);
+        assertEquals(methodAnnotations.length, 1);
+
+        char elementChar = ((MethodAnnotation)methodAnnotations[0]).elementChar();
+        assertEquals('X', elementChar);
+    }
+
+    /**
+     *  Test adding a method annotation with new value of Double element.
+     */
+    @Test
+    public void addMethodAnnotationWithDoubleElement() throws Exception {
+        MethodId<?, Void> methodId = generateVoidMethod(TypeId.DOUBLE);
+        AnnotationId.Element element = new AnnotationId.Element("elementDouble", Double.NaN);
+        addAnnotationToMethod(methodId, element);
+
+        Annotation[] methodAnnotations = getMethodAnnotations(methodId);
+        assertEquals(methodAnnotations.length, 1);
+
+        double elementDouble = ((MethodAnnotation)methodAnnotations[0]).elementDouble();
+        assertEquals(Double.NaN, elementDouble, 0);
+    }
+
+    /**
+     *  Test adding a method annotation with new value of Float element.
+     */
+    @Test
+    public void addMethodAnnotationWithFloatElement() throws Exception {
+        MethodId<?, Void> methodId = generateVoidMethod(TypeId.FLOAT);
+        AnnotationId.Element element = new AnnotationId.Element("elementFloat", Float.NaN);
+        addAnnotationToMethod(methodId, element);
+
+        Annotation[] methodAnnotations = getMethodAnnotations(methodId);
+        assertEquals(methodAnnotations.length, 1);
+
+        float elementFloat = ((MethodAnnotation)methodAnnotations[0]).elementFloat();
+        assertEquals(Float.NaN, elementFloat, 0);
+    }
+
+    /**
+     *  Test adding a method annotation with new value of Int element.
+     */
+    @Test
+    public void addMethodAnnotationWithIntElement() throws Exception {
+        MethodId<?, Void> methodId = generateVoidMethod(TypeId.INT);
+        AnnotationId.Element element = new AnnotationId.Element("elementInt", Integer.MAX_VALUE);
+        addAnnotationToMethod(methodId, element);
+
+        Annotation[] methodAnnotations = getMethodAnnotations(methodId);
+        assertEquals(methodAnnotations.length, 1);
+
+        int elementInt = ((MethodAnnotation)methodAnnotations[0]).elementInt();
+        assertEquals(Integer.MAX_VALUE, elementInt);
+    }
+
+    /**
+     *  Test adding a method annotation with new value of Long element.
+     */
+    @Test
+    public void addMethodAnnotationWithLongElement() throws Exception {
+        MethodId<?, Void> methodId = generateVoidMethod(TypeId.LONG);
+        AnnotationId.Element element = new AnnotationId.Element("elementLong", Long.MAX_VALUE);
+        addAnnotationToMethod(methodId, element);
+
+        Annotation[] methodAnnotations = getMethodAnnotations(methodId);
+        assertEquals(methodAnnotations.length, 1);
+
+        long elementLong = ((MethodAnnotation)methodAnnotations[0]).elementLong();
+        assertEquals(Long.MAX_VALUE, elementLong);
+    }
+
+    /**
+     *  Test adding a method annotation with new value of Short element.
+     */
+    @Test
+    public void addMethodAnnotationWithShortElement() throws Exception {
+        MethodId<?, Void> methodId = generateVoidMethod(TypeId.SHORT);
+        AnnotationId.Element element = new AnnotationId.Element("elementShort", Short.MAX_VALUE);
+        addAnnotationToMethod(methodId, element);
+
+        Annotation[] methodAnnotations = getMethodAnnotations(methodId);
+        assertEquals(methodAnnotations.length, 1);
+
+        short elementShort = ((MethodAnnotation)methodAnnotations[0]).elementShort();
+        assertEquals(Short.MAX_VALUE, elementShort);
+    }
+
+    /**
+     *  Test adding a method annotation with new value of String element.
+     */
+    @Test
+    public void addMethodAnnotationWithStingElement() throws Exception {
+        MethodId<?, Void> methodId = generateVoidMethod(TypeId.STRING);
+        AnnotationId.Element element = new AnnotationId.Element("elementString", "hello");
+        addAnnotationToMethod(methodId, element);
+
+        Annotation[] methodAnnotations = getMethodAnnotations(methodId);
+        assertEquals(methodAnnotations.length, 1);
+
+        String elementString = ((MethodAnnotation)methodAnnotations[0]).elementString();
+        assertEquals("hello", elementString);
+    }
+
+    /**
+     *  Test adding a method annotation with new value of Enum element.
+     */
+    @Test
+    public void addMethodAnnotationWithEnumElement() throws Exception {
+        MethodId<?, Void> methodId = generateVoidMethod(TypeId.get(Enum.class));
+        AnnotationId.Element element = new AnnotationId.Element("elementEnum", ElementEnum.INSTANCE_1);
+        addAnnotationToMethod(methodId, element);
+
+        Annotation[] methodAnnotations = getMethodAnnotations(methodId);
+        assertEquals(methodAnnotations.length, 1);
+
+        ElementEnum elementEnum = ((MethodAnnotation)methodAnnotations[0]).elementEnum();
+        assertEquals(ElementEnum.INSTANCE_1, elementEnum);
+    }
+
+    /**
+     *  Test adding a method annotation with new value of Class element.
+     */
+    @Test
+    public void addMethodAnnotationWithClassElement() throws Exception {
+        MethodId<?, Void> methodId = generateVoidMethod(TypeId.get(AnnotationId.class));
+        AnnotationId.Element element = new AnnotationId.Element("elementClass", AnnotationId.class);
+        addAnnotationToMethod(methodId, element);
+
+        Annotation[] methodAnnotations = getMethodAnnotations(methodId);
+        assertEquals(methodAnnotations.length, 1);
+
+        Class<?> elementClass = ((MethodAnnotation)methodAnnotations[0]).elementClass();
+        assertEquals(AnnotationId.class, elementClass);
+    }
+
+    /**
+     *  Test adding a method annotation with new multiple values of an element.
+     */
+    @Test
+    public void addMethodAnnotationWithMultiElements() throws Exception {
+        MethodId<?, Void> methodId = generateVoidMethod();
+        AnnotationId.Element element1 = new AnnotationId.Element("elementClass", AnnotationId.class);
+        AnnotationId.Element element2 = new AnnotationId.Element("elementEnum", ElementEnum.INSTANCE_1);
+        AnnotationId.Element[] elements = {element1, element2};
+        addAnnotationToMethod(methodId, elements);
+
+        Annotation[] methodAnnotations = getMethodAnnotations(methodId);
+        assertEquals(methodAnnotations.length, 1);
+
+        ElementEnum elementEnum = ((MethodAnnotation)methodAnnotations[0]).elementEnum();
+        assertEquals(ElementEnum.INSTANCE_1, elementEnum);
+        Class<?> elementClass = ((MethodAnnotation)methodAnnotations[0]).elementClass();
+        assertEquals(AnnotationId.class, elementClass);
+    }
+
+    /**
+     *  Test adding a method annotation with duplicate values of an element. The previous value will
+     *  be replaced by latter one.
+     */
+    @Test
+    public void addMethodAnnotationWithDuplicateElements() throws Exception {
+        MethodId<?, Void> methodId = generateVoidMethod();
+        AnnotationId.Element element1 = new AnnotationId.Element("elementEnum", ElementEnum.INSTANCE_1);
+        AnnotationId.Element element2 = new AnnotationId.Element("elementEnum", ElementEnum.INSTANCE_0);
+        addAnnotationToMethod(methodId, element1, element2);
+
+        Annotation[] methodAnnotations = getMethodAnnotations(methodId);
+        assertEquals(methodAnnotations.length, 1);
+
+        ElementEnum elementEnum = ((MethodAnnotation)methodAnnotations[0]).elementEnum();
+        assertEquals(ElementEnum.INSTANCE_0, elementEnum);
+    }
+
+
+    /**
+     *  Test adding a method annotation with new array value of an element. It's not supported yet.
+     */
+    @Test
+    public void addMethodAnnotationWithArrayElementValue() {
+        try {
+            MethodId<?, Void> methodId = generateVoidMethod();
+            int[] a = {1, 2};
+            AnnotationId.Element element = new AnnotationId.Element("elementInt", a);
+            addAnnotationToMethod(methodId, element);
+            fail();
+        } catch (UnsupportedOperationException e) {
+            System.out.println(e);
+        }
+    }
+
+    /**
+     *  Test adding a method annotation with new TypeId value of an element. It's not supported yet.
+     */
+    @Test
+    public void addMethodAnnotationWithTypeIdElementValue() {
+        try {
+            MethodId<?, Void> methodId = generateVoidMethod();
+            AnnotationId.Element element = new AnnotationId.Element("elementInt", INT);
+            addAnnotationToMethod(methodId, element);
+            fail();
+        } catch (UnsupportedOperationException e) {
+            System.out.println(e);
+        }
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+    /**
+     *  Internal methods
+     */
+    private void init() {
+        clearDataDirectory();
+
+        dexMaker = new DexMaker();
+        dexMaker.declare(GENERATED, "Generated.java", PUBLIC, TypeId.OBJECT);
+    }
+
+    private void clearDataDirectory() {
+        for (File f : getDataDirectory().listFiles()) {
+            if (f.getName().endsWith(".jar") || f.getName().endsWith(".dex")) {
+                f.delete();
+            }
+        }
+    }
+
+    private static File getDataDirectory() {
+        String dataDir = InstrumentationRegistry.getTargetContext().getApplicationInfo().dataDir;
+        return new File(dataDir + "/cache" );
+    }
+
+    private MethodId<?, Void> generateVoidMethod(TypeId<?>... parameters) {
+        MethodId<?, Void> methodId = GENERATED.getMethod(VOID, "call", parameters);
+        Code code = dexMaker.declare(methodId, PUBLIC);
+        code.returnVoid();
+        return methodId;
+    }
+
+    private void addAnnotationToMethod(MethodId<?, Void> methodId, AnnotationId.Element... elements) {
+        TypeId<MethodAnnotation> annotationTypeId = TypeId.get(MethodAnnotation.class);
+        AnnotationId<?, MethodAnnotation> annotationId = AnnotationId.get(GENERATED, annotationTypeId, ElementType.METHOD);
+        for (AnnotationId.Element element : elements) {
+            annotationId.set(element);
+        }
+        annotationId.addToMethod(dexMaker, methodId);
+    }
+
+    private Annotation[] getMethodAnnotations(MethodId<?, Void> methodId) throws Exception {
+        Class<?> generatedClass = generateAndLoad();
+        Class<?>[] parameters = getMethodParameters(methodId);
+        Method method = generatedClass.getMethod(methodId.getName(), parameters);
+        return method.getAnnotations();
+    }
+
+    private Class<?>[] getMethodParameters(MethodId<?, Void> methodId) throws ClassNotFoundException {
+        List<TypeId<?>> paras = methodId.getParameters();
+        Class<?>[] p = null;
+        if (paras.size() > 0) {
+            p = new Class<?>[paras.size()];
+            for (int i = 0; i < paras.size(); i++) {
+                p[i] = TYPE_TO_PRIMITIVE.get(paras.get(i));
+                if (p[i] == null) {
+                    String name = paras.get(i).getName().replace('/', '.');
+                    if (name.charAt(0) == 'L') {
+                        name = name.substring(1, name.length()-1);
+                    }
+                    p[i] = Class.forName(name);
+                }
+            }
+        }
+        return p;
+    }
+
+    private Class<?> generateAndLoad() throws Exception {
+        return dexMaker.generateAndLoad(getClass().getClassLoader(), getDataDirectory())
+                .loadClass("Generated");
+    }
+}
diff --git a/dexmaker/src/main/java/com/android/dx/AnnotationId.java b/dexmaker/src/main/java/com/android/dx/AnnotationId.java
new file mode 100644
index 0000000..bcc201b
--- /dev/null
+++ b/dexmaker/src/main/java/com/android/dx/AnnotationId.java
@@ -0,0 +1,257 @@
+/*
+ * 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.dx;
+
+import com.android.dx.dex.file.ClassDefItem;
+import com.android.dx.rop.annotation.Annotation;
+import com.android.dx.rop.annotation.AnnotationVisibility;
+import com.android.dx.rop.annotation.Annotations;
+import com.android.dx.rop.annotation.NameValuePair;
+import com.android.dx.rop.cst.*;
+
+import java.lang.annotation.ElementType;
+import java.util.HashMap;
+
+/**
+ * Identifies an annotation on a program element, see {@link java.lang.annotation.ElementType}.
+ *
+ * Currently it is only targeting Class, Method, Field and Parameter because those are supported by
+ * {@link com.android.dx.dex.file.AnnotationsDirectoryItem} so far.
+ *
+ * <p><strong>NOTE:</strong>
+ * So far it only supports adding method annotation. The annotation of class, field and parameter
+ * will be implemented later.
+ *
+ * <p><strong>WARNING:</strong>
+ * The declared element of an annotation type should either have a default value or be set a value via
+ * {@code AnnotationId.set(Element)}. Otherwise it will incur
+ * {@link java.lang.annotation.IncompleteAnnotationException} when accessing the annotation element
+ * through reflection. The example is as follows:
+ * <pre>
+ *     {@code @Retention(RetentionPolicy.RUNTIME)}
+ *     {@code @Target({ElementType.METHOD})}
+ *     {@code @interface MethodAnnotation {
+ *                boolean elementBoolean();
+ *                // boolean elementBoolean() default false;
+ *            }
+ *
+ *            TypeId<?> GENERATED = TypeId.get("LGenerated;");
+ *            MethodId<?, Void> methodId = GENERATED.getMethod(VOID, "call");
+ *            Code code = dexMaker.declare(methodId, PUBLIC);
+ *            code.returnVoid();
+ *
+ *            TypeId<MethodAnnotation> annotationTypeId = TypeId.get(MethodAnnotation.class);
+ *            AnnotationId<?, MethodAnnotation> annotationId = AnnotationId.get(GENERATED,
+ *              annotationTypeId, ElementType.METHOD);
+ *
+ *            AnnotationId.Element element = new AnnotationId.Element("elementBoolean", true);
+ *            annotationId.set(element);
+ *            annotationId.addToMethod(dexMaker, methodId);
+ *     }
+ * </pre>
+ *
+ * @param <D> the type that declares the program element.
+ * @param <V> the annotation type. It should be a known type before compile.
+ */
+public final class AnnotationId<D, V> {
+    private final TypeId<D> declaringType;
+    private final TypeId<V> type;
+    /** The type of program element to be annotated */
+    private final ElementType annotatedElement;
+    /** The elements this annotation holds */
+    private final HashMap<String, NameValuePair> elements;
+
+    private AnnotationId(TypeId<D> declaringType, TypeId<V> type, ElementType annotatedElement) {
+        this.declaringType = declaringType;
+        this.type = type;
+        this.annotatedElement = annotatedElement;
+        this.elements = new HashMap<>();
+    }
+
+    /**
+     *  Construct an instance. It initially contains no elements.
+     *
+     * @param declaringType the type declaring the program element.
+     * @param type the annotation type.
+     * @param annotatedElement the program element type to be annotated.
+     * @return an annotation {@code AnnotationId<D,V>} instance.
+     */
+    public static <D, V> AnnotationId<D, V> get(TypeId<D> declaringType, TypeId<V> type,
+                                                ElementType annotatedElement) {
+        if (annotatedElement != ElementType.TYPE &&
+                annotatedElement != ElementType.METHOD &&
+                annotatedElement != ElementType.FIELD &&
+                annotatedElement != ElementType.PARAMETER) {
+            throw new IllegalArgumentException("element type is not supported to annotate yet.");
+        }
+
+        return new AnnotationId<>(declaringType, type, annotatedElement);
+    }
+
+    /**
+     * Set an annotation element of this instance.
+     * If there is a preexisting element with the same name, it will be
+     * replaced by this method.
+     *
+     * @param element {@code non-null;} the annotation element to be set.
+     */
+    public void set(Element element) {
+        if (element == null) {
+            throw new NullPointerException("element == null");
+        }
+
+        CstString pairName = new CstString(element.getName());
+        Constant pairValue = Element.toConstant(element.getValue());
+        NameValuePair nameValuePair = new NameValuePair(pairName, pairValue);
+        elements.put(element.getName(), nameValuePair);
+    }
+
+    /**
+     * Add this annotation to a method.
+     *
+     * @param dexMaker DexMaker instance.
+     * @param method Method to be added to.
+     */
+    public void addToMethod(DexMaker dexMaker, MethodId<?, ?> method) {
+        if (annotatedElement != ElementType.METHOD) {
+            throw new IllegalStateException("This annotation is not for method");
+        }
+
+        if (method.declaringType != declaringType) {
+            throw new IllegalArgumentException("Method" + method + "'s declaring type is inconsistent with" + this);
+        }
+
+        ClassDefItem classDefItem = dexMaker.getTypeDeclaration(declaringType).toClassDefItem();
+
+        if (classDefItem == null) {
+            throw new NullPointerException("No class defined item is found");
+        } else {
+            CstMethodRef cstMethodRef = method.constant;
+
+            if (cstMethodRef == null) {
+                throw new NullPointerException("Method reference is NULL");
+            } else {
+                // Generate CstType
+                CstType cstType = CstType.intern(type.ropType);
+
+                // Generate Annotation
+                Annotation annotation = new Annotation(cstType, AnnotationVisibility.RUNTIME);
+
+                // Add generated annotation
+                Annotations annotations = new Annotations();
+                for (NameValuePair nvp : elements.values()) {
+                    annotation.add(nvp);
+                }
+                annotations.add(annotation);
+                classDefItem.addMethodAnnotations(cstMethodRef, annotations, dexMaker.getDexFile());
+            }
+        }
+    }
+
+    /**
+     *  A wrapper of <code>NameValuePair</code> represents a (name, value) pair used as the contents
+     *  of an annotation.
+     *
+     *  An {@code Element} instance is stored in {@code AnnotationId.elements} by calling {@code
+     *  AnnotationId.set(Element)}.
+     *
+     *  <p><strong>WARNING: </strong></p>
+     *  the name should be exact same as the annotation element declared in the annotation type
+     *  which is referred by field {@code AnnotationId.type},otherwise the annotation will fail
+     *  to add and {@code java.lang.reflect.Method.getAnnotations()} will return nothing.
+     *
+     */
+    public static final class Element {
+        /** {@code non-null;} the name */
+        private final String name;
+        /** {@code non-null;} the value */
+        private final Object value;
+
+        /**
+         * Construct an instance.
+         *
+         * @param name {@code non-null;} the name
+         * @param value {@code non-null;} the value
+         */
+        public Element(String name, Object value) {
+            if (name == null) {
+                throw new NullPointerException("name == null");
+            }
+
+            if (value == null) {
+                throw new NullPointerException("value == null");
+            }
+            this.name = name;
+            this.value = value;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public Object getValue() {
+            return value;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public String toString() {
+            return "[" + name + ", " + value + "]";
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public int hashCode() {
+            return name.hashCode() * 31 + value.hashCode();
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public boolean equals(Object other) {
+            if (! (other instanceof Element)) {
+                return false;
+            }
+
+            Element otherElement = (Element) other;
+
+            return name.equals(otherElement.name)
+                    && value.equals(otherElement.value);
+        }
+
+        /**
+         *  Convert a value of an element to a {@code Constant}.
+         *  <p><strong>Warning:</strong> Array or TypeId value is not supported yet.
+         *
+         * @param value an annotation element value.
+         * @return a Constant
+         */
+        static Constant toConstant(Object value) {
+            Class clazz = value.getClass();
+            if (clazz.isEnum()) {
+                CstString descriptor = new CstString(TypeId.get(clazz).getName());
+                CstString name = new CstString(((Enum)value).name());
+                CstNat cstNat = new CstNat(name, descriptor);
+                return new CstEnumRef(cstNat);
+            } else if (clazz.isArray()) {
+                throw new UnsupportedOperationException("Array is not supported yet");
+            } else if (value instanceof TypeId) {
+                throw new UnsupportedOperationException("TypeId is not supported yet");
+            } else {
+                return  Constants.getConstant(value);
+            }
+        }
+    }
+}
diff --git a/dexmaker/src/main/java/com/android/dx/DexMaker.java b/dexmaker/src/main/java/com/android/dx/DexMaker.java
index f10ad8e..755c9fa 100644
--- a/dexmaker/src/main/java/com/android/dx/DexMaker.java
+++ b/dexmaker/src/main/java/com/android/dx/DexMaker.java
@@ -49,6 +49,8 @@
 import static java.lang.reflect.Modifier.PRIVATE;
 import static java.lang.reflect.Modifier.STATIC;
 
+import android.util.Log;
+
 /**
  * Generates a <strong>D</strong>alvik <strong>EX</strong>ecutable (dex)
  * file for execution on Android. Dex files define classes and interfaces,
@@ -196,8 +198,12 @@
  * }</pre>
  */
 public final class DexMaker {
+    private static final String LOG_TAG = DexMaker.class.getSimpleName();
+
     private final Map<TypeId<?>, TypeDeclaration> types = new LinkedHashMap<>();
     private ClassLoader sharedClassLoader;
+    private DexFile outputDex;
+    private boolean markAsTrusted;
 
     /**
      * Creates a new {@code DexMaker} instance, which can be used to create a
@@ -206,7 +212,7 @@
     public DexMaker() {
     }
 
-    private TypeDeclaration getTypeDeclaration(TypeId<?> type) {
+    TypeDeclaration getTypeDeclaration(TypeId<?> type) {
         TypeDeclaration result = types.get(type);
         if (result == null) {
             result = new TypeDeclaration(type);
@@ -313,9 +319,11 @@
      * Generates a dex file and returns its bytes.
      */
     public byte[] generate() {
-        DexOptions options = new DexOptions();
-        options.targetApiLevel = DexFormat.API_NO_EXTENDED_OPCODES;
-        DexFile outputDex = new DexFile(options);
+        if (outputDex == null) {
+            DexOptions options = new DexOptions();
+            options.targetApiLevel = DexFormat.API_NO_EXTENDED_OPCODES;
+            outputDex = new DexFile(options);
+        }
 
         for (TypeDeclaration typeDeclaration : types.values()) {
             outputDex.add(typeDeclaration.toClassDefItem());
@@ -356,12 +364,51 @@
         return "Generated_" + checksum +".jar";
     }
 
+    /**
+     * Set shared class loader to use.
+     *
+     * <p>If a class wants to call package private methods of another class they need to share a
+     * class loader. One common case for this requirement is a mock class wanting to mock package
+     * private methods of the original class.
+     *
+     * @param classLoader the class loader the new class should be loaded by
+     */
     public void setSharedClassLoader(ClassLoader classLoader) {
         this.sharedClassLoader = classLoader;
     }
 
+    public void markAsTrusted() {
+        this.markAsTrusted = true;
+    }
+
     private ClassLoader generateClassLoader(File result, File dexCache, ClassLoader parent) {
         try {
+            // Try to load the class so that it can call hidden APIs. This is required for spying
+            // on system classes as real-methods of these classes might call blacklisted APIs
+            if (markAsTrusted) {
+                try {
+                    if (sharedClassLoader != null) {
+                        ClassLoader loader = parent != null ? parent : sharedClassLoader;
+                        loader.getClass().getMethod("addDexPath", String.class,
+                                Boolean.TYPE).invoke(loader, result.getPath(), true);
+                        return loader;
+                    } else {
+                        return (ClassLoader) Class.forName("dalvik.system.BaseDexClassLoader")
+                                .getConstructor(String.class, File.class, String.class,
+                                        ClassLoader.class, Boolean.TYPE)
+                                .newInstance(result.getPath(), dexCache.getAbsoluteFile(), null,
+                                        parent, true);
+                    }
+                } catch (InvocationTargetException e) {
+                    if (e.getCause() instanceof SecurityException) {
+                        Log.i(LOG_TAG, "Cannot allow to call blacklisted super methods. This might "
+                                + "break spying on system classes.", e.getCause());
+                    } else {
+                        throw e;
+                    }
+                }
+            }
+
             if (sharedClassLoader != null) {
                 ClassLoader loader = parent != null ? parent : sharedClassLoader;
                 loader.getClass().getMethod("addDexPath", String.class).invoke(loader,
@@ -451,7 +498,16 @@
         return generateClassLoader(result, dexCache, parent);
     }
 
-    private static class TypeDeclaration {
+    DexFile getDexFile() {
+        if (outputDex == null) {
+            DexOptions options = new DexOptions();
+            options.targetApiLevel = DexFormat.API_NO_EXTENDED_OPCODES;
+            outputDex = new DexFile(options);
+        }
+        return outputDex;
+    }
+
+    static class TypeDeclaration {
         private final TypeId<?> type;
 
         /** declared state */
@@ -460,6 +516,7 @@
         private TypeId<?> supertype;
         private String sourceFile;
         private TypeList interfaces;
+        private ClassDefItem classDefItem;
 
         private final Map<FieldId, FieldDeclaration> fields = new LinkedHashMap<>();
         private final Map<MethodId, MethodDeclaration> methods = new LinkedHashMap<>();
@@ -479,27 +536,29 @@
 
             CstType thisType = type.constant;
 
-            ClassDefItem out = new ClassDefItem(thisType, flags, supertype.constant,
-                    interfaces.ropTypes, new CstString(sourceFile));
+            if (classDefItem == null) {
+                classDefItem = new ClassDefItem(thisType, flags, supertype.constant,
+                        interfaces.ropTypes, new CstString(sourceFile));
 
-            for (MethodDeclaration method : methods.values()) {
-                EncodedMethod encoded = method.toEncodedMethod(dexOptions);
-                if (method.isDirect()) {
-                    out.addDirectMethod(encoded);
-                } else {
-                    out.addVirtualMethod(encoded);
+                for (MethodDeclaration method : methods.values()) {
+                    EncodedMethod encoded = method.toEncodedMethod(dexOptions);
+                    if (method.isDirect()) {
+                        classDefItem.addDirectMethod(encoded);
+                    } else {
+                        classDefItem.addVirtualMethod(encoded);
+                    }
                 }
-            }
-            for (FieldDeclaration field : fields.values()) {
-                EncodedField encoded = field.toEncodedField();
-                if (field.isStatic()) {
-                    out.addStaticField(encoded, Constants.getConstant(field.staticValue));
-                } else {
-                    out.addInstanceField(encoded);
+                for (FieldDeclaration field : fields.values()) {
+                    EncodedField encoded = field.toEncodedField();
+                    if (field.isStatic()) {
+                        classDefItem.addStaticField(encoded, Constants.getConstant(field.staticValue));
+                    } else {
+                        classDefItem.addInstanceField(encoded);
+                    }
                 }
             }
 
-            return out;
+            return classDefItem;
         }
     }
 
diff --git a/dexmaker/src/main/java/com/android/dx/stock/ProxyBuilder.java b/dexmaker/src/main/java/com/android/dx/stock/ProxyBuilder.java
index 1363894..053fb16 100644
--- a/dexmaker/src/main/java/com/android/dx/stock/ProxyBuilder.java
+++ b/dexmaker/src/main/java/com/android/dx/stock/ProxyBuilder.java
@@ -47,6 +47,8 @@
 import static java.lang.reflect.Modifier.PUBLIC;
 import static java.lang.reflect.Modifier.STATIC;
 
+import android.os.Build;
+
 /**
  * Creates dynamic proxies of concrete classes.
  * <p>
@@ -301,6 +303,18 @@
         if (sharedClassLoader) {
             dexMaker.setSharedClassLoader(baseClass.getClassLoader());
         }
+        if (Build.VERSION.SDK_INT >= 28) {
+            // The proxied class might have blacklisted methods. Blacklisting methods (and fields)
+            // is a new feature of Android P:
+            //
+            // https://android-developers.googleblog.com/2018/02/
+            // improving-stability-by-reducing-usage.html
+            //
+            // The newly generated class might not be allowed to call methods of the proxied class
+            // if it is not trusted. As it is not clear which classes have blacklisted methods, mark
+            // all generated classes as trusted.
+            dexMaker.markAsTrusted();
+        }
         ClassLoader classLoader = dexMaker.generateAndLoad(parentClassLoader, dexCache);
         try {
             proxyClass = loadClass(classLoader, generatedName);