DO NOT MERGE - Merge pi-platform-release (PPRL.190505.001) into stage-aosp-master

Bug: 132622481
Change-Id: I6fcfda743d027458441fa1ff5329cd067633154d
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index 98b50ee..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-# IntelliJ IDEA
-.idea/
-*.iml
diff --git a/Android.bp b/Android.bp
index 61b24ec..07ba414 100644
--- a/Android.bp
+++ b/Android.bp
@@ -20,16 +20,15 @@
 
     static_libs: [
         "dexmaker-dx-target",
-        "dexmaker-dex-target",
     ],
 
-    sdk_version: "10",
+    sdk_version: "core_current",
 }
 
 // Build Dexmaker's MockMaker, a plugin to Mockito
 java_library_static {
     name: "dexmaker-mockmaker",
-    sdk_version: "10",
+    sdk_version: "core_current",
     srcs: ["dexmaker-mockito/src/main/java/**/*.java"],
     java_resource_dirs: ["dexmaker-mockito/src/main/resources"],
     libs: [
@@ -78,11 +77,8 @@
         "libz",
     ],
 
-    // As an NDK-based library we cannot depend on libopenjdkjvmti_headers.
     include_dirs: [
         "art/openjdkjvmti/include",
-        // TODO Remove once upstream has updated to new slicer.
-        "tools/dexter/slicer/export/slicer",
     ],
 }
 
@@ -95,6 +91,15 @@
     srcs: ["dexmaker-mockito-inline/src/main/jni/**/*.cc"],
 }
 
+// Build agent for Dexmaker's extended MockMaker
+cc_library_shared {
+    name: "libstaticjvmtiagent",
+    defaults: [
+        "dexmaker_agent_defaults",
+    ],
+    srcs: ["dexmaker-mockito-inline-extended/src/main/jni/**/*.cc"],
+}
+
 // Build agent for Dexmaker's inline tests
 cc_library_shared {
     name: "libmultiplejvmtiagentsinterferenceagent",
@@ -115,20 +120,192 @@
         "mockito-api",
     ],
     required: ["libdexmakerjvmtiagent"],
+}
 
-    errorprone: {
-        javacflags: [
-            "-Xep:CollectionIncompatibleType:WARN"
-        ],
-    }
+// Build Dexmaker's extended MockMaker, a plugin to Mockito
+java_library_static {
+    name: "dexmaker-extended-mockmaker",
+    sdk_version: "current",
+    srcs: ["dexmaker-mockito-inline-extended/src/main/java/**/*.java"],
+
+    java_resource_dirs: ["dexmaker-mockito-inline/src/main/resources"],
+
+    libs: [
+        "dexmaker",
+        "mockito-api",
+        "dexmaker-inline-mockmaker",
+    ],
+    required: [
+        "libdexmakerjvmtiagent",
+        "libstaticjvmtiagent"
+    ],
+}
+
+// Provides mockito functionality for on-device tests. Does not allow stubbing of final or static
+// methods.
+java_library_static {
+    name: "mockito-target",
+    no_framework_libs: true,
+    static_libs: [
+        "mockito-target-minus-junit4",
+        "junit",
+    ],
+    sdk_version: "core_current",
+}
+
+// Same as mockito-target but does not bundle junit
+java_library_static {
+    name: "mockito-target-minus-junit4",
+    no_framework_libs: true,
+    static_libs: [
+        "mockito",
+        "dexmaker",
+        "dexmaker-mockmaker",
+        "objenesis",
+    ],
+    libs: ["junit"],
+    sdk_version: "core_current",
+
+    java_version: "1.7",
+}
+
+// Provides mockito functionality for on-device tests. Allows stubbing of final methods. Does not
+// allow stubbing of static methods.
+// Project depending on this also need to depend on the static JNI library libdexmakerjvmtiagent
+java_library_static {
+    name: "mockito-target-inline",
+    no_framework_libs: true,
+    static_libs: [
+        "mockito-target-inline-minus-junit4",
+        "junit",
+    ],
+    sdk_version: "current",
+}
+
+// Same as mockito-target-inline but does not bundle junit
+java_library_static {
+    name: "mockito-target-inline-minus-junit4",
+    no_framework_libs: true,
+    static_libs: [
+        "mockito",
+        "dexmaker",
+        "dexmaker-inline-mockmaker",
+        "objenesis",
+    ],
+    libs: ["junit"],
+    sdk_version: "current",
+
+    java_version: "1.7",
+}
+
+// Provides mockito functionality for on-device tests. Allows stubbing of final and static methods.
+// Stubbing static methods is not an official mockito API.
+// Project depending on this also need to depend on the static JNI libraries libstaticjvmtiagent and
+// libdexmakerjvmtiagent
+java_library_static {
+    name: "mockito-target-extended",
+    no_framework_libs: true,
+    static_libs: [
+        "mockito-target-extended-minus-junit4",
+        "junit",
+    ],
+    sdk_version: "current",
+}
+
+// Same as mockito-target-extended but does not bundle junit
+java_library_static {
+    name: "mockito-target-extended-minus-junit4",
+    no_framework_libs: true,
+    static_libs: [
+        "mockito",
+        "dexmaker",
+        "dexmaker-inline-mockmaker",
+        "dexmaker-extended-mockmaker",
+        "objenesis",
+    ],
+    libs: ["junit"],
+    sdk_version: "current",
+
+    java_version: "1.7",
 }
 
 java_import {
     name: "dexmaker-dx-target",
-    jars: ["lib/dalvik-dx-1.jar"],
+    jars: ["lib/dalvik-dx-9.0.0_r3.jar"],
 }
 
-java_import {
-    name: "dexmaker-dex-target",
-    jars: ["lib/libcore-dex-2.jar"],
+// dexmaker tests
+java_library_static {
+    name: "dexmaker-tests-lib",
+    sdk_version: "current",
+    srcs: ["dexmaker-tests/src/androidTest/java/**/*.java"],
+
+    libs: [
+        "androidx.test.rules",
+        "dexmaker",
+        "junit",
+    ],
+}
+
+// dexmaker-mockito tests
+java_library_static {
+    name: "dexmaker-mockmaker-tests",
+    sdk_version: "current",
+    srcs: ["dexmaker-mockito-tests/src/main/java/**/*.java"],
+
+    libs: [
+        "androidx.test.rules",
+        "dexmaker",
+        "mockito",
+        "junit",
+        "dexmaker-mockmaker",
+    ],
+}
+
+// dexmaker-mockito-inline tests
+java_library_static {
+    name: "dexmaker-inline-mockmaker-tests",
+    sdk_version: "current",
+    srcs: ["dexmaker-mockito-inline-tests/src/main/java/**/*.java"],
+
+    libs: [
+        "androidx.test.rules",
+        "dexmaker",
+        "android-support-v4",
+        "mockito",
+        "junit",
+        "dexmaker-inline-mockmaker",
+    ],
+}
+
+// dexmaker-mockito-extended tests
+java_library_static {
+    name: "dexmaker-extended-mockmaker-tests",
+    sdk_version: "current",
+    srcs: ["dexmaker-mockito-inline-extended-tests/src/main/java/**/*.java"],
+
+    libs: [
+        "androidx.test.rules",
+        "dexmaker",
+        "android-support-v4",
+        "mockito",
+        "junit",
+        "dexmaker-extended-mockmaker",
+    ],
+}
+
+/*
+ * To run the tests:
+ *   atest DexmakerTests
+ */
+
+android_test {
+    name: "DexmakerTests",
+    sdk_version: "current",
+    static_libs: [
+        "dexmaker-tests-lib",
+        "dexmaker",
+        "androidx.test.rules",
+        "junit",
+    ],
 }
diff --git a/Android.mk b/Android.mk
deleted file mode 100644
index 8bf81fb..0000000
--- a/Android.mk
+++ /dev/null
@@ -1,17 +0,0 @@
-# Copyright (C) 2011 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.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 2c88308..3a3d0aa 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1,12 +1,26 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.google.dexmaker.tests" >
+<!--
+  Copyright (C) 2018 The Android Open Source Project
 
-    <application>
-        <uses-library android:name="android.test.runner" />
-    </application>
+  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
 
-    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.google.dexmaker.tests"
-                     android:label="Dexmaker Tests"/>
+       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.
+  -->
+<manifest package="com.linkedin.dexmaker"
+          xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+
+    <application android:allowBackup="false" android:debuggable="true" />
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.linkedin.dexmaker" />
 </manifest>
diff --git a/README.version b/README.version
index 7f1bbd0..a4a21f2 100644
--- a/README.version
+++ b/README.version
@@ -1,5 +1,5 @@
 URL: https://github.com/linkedin/dexmaker/
-Version: master (5fb49bba98647d7a0aeea0cbf91fd670c3ff552a)
+Version: master (8ff85edb2793cc1e0f6a93c67b127cb9c43d924e)
 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.
@@ -9,7 +9,8 @@
 It includes a stock code generator for class proxies. If you just want to do AOP or class mocking, you don't need to mess around with bytecodes.
 
 Local Modifications:
-        Allow to share classloader via dexmaker.share_classloader system property (I8c2490c3ec8e8582dc41c486f8f7a406bd635ebb)
-	Allow 'Q' until we can replace the version check with a number based check
-        Mark mocks as trusted (needs upstreaming)
-	Minimal change to correctly guess data directory when running in secondary users
\ No newline at end of file
+        Add ability to run dexmaker tests from within the source tree (I1b146841099b54f64d4a7dfe743b88717793619a)
+        Allow to share classloader via dexmaker.share_classloader system property (I324cddd644610eef811c620a1fccf6a24b2b9406)
+        Do not read Build.VERSION to allow non-standard Android distributions (Ia8c4ba4c82cd6f193c565f1bfe48faffc4aac08f)
+        Temporarily ignore failing test (Ibf7b6c2eb05c5ff83f0817f9224369e20c0b775d)
+        Restrict InspectClass to current thread. (Ic62951ff81bed60ac7512455fad65210e4b728a9, need upstreaming)
diff --git a/TEST_MAPPING b/TEST_MAPPING
new file mode 100644
index 0000000..a4c5bba
--- /dev/null
+++ b/TEST_MAPPING
@@ -0,0 +1,16 @@
+{
+    "presubmit": [
+        {
+            "name": "CtsMockingTestCases"
+        },
+        {
+            "name": "CtsInlineMockingTestCases"
+        },
+        {
+            "name": "CtsMockingDebuggableTestCases"
+        },
+        {
+            "name": "CtsExtendedMockingTestCases"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/bug-8108255.patch b/bug-8108255.patch
deleted file mode 100644
index bdccc8e..0000000
--- a/bug-8108255.patch
+++ /dev/null
@@ -1,16 +0,0 @@
-diff -ur a/mockito/src/main/java/com/google/dexmaker/mockito/DexmakerMockMaker.java b/mockito/src/main/java/com/google/dexmaker/mockito/DexmakerMockMaker.java
---- a/mockito/src/main/java/com/google/dexmaker/mockito/DexmakerMockMaker.java	2013-01-07 11:27:13.000000000 -0800
-+++ b/mockito/src/main/java/com/google/dexmaker/mockito/DexmakerMockMaker.java	2013-02-15 11:27:44.000000000 -0800
-@@ -45,9 +45,10 @@
-             Class[] classesToMock = new Class[extraInterfaces.length + 1];
-             classesToMock[0] = typeToMock;
-             System.arraycopy(extraInterfaces, 0, classesToMock, 1, extraInterfaces.length);
-+            ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
-             @SuppressWarnings("unchecked") // newProxyInstance returns the type of typeToMock
--            T mock = (T) Proxy.newProxyInstance(typeToMock.getClassLoader(),
--                    classesToMock, invocationHandler);
-+            T mock = (T) Proxy.newProxyInstance(contextClassLoader, classesToMock,
-+                    invocationHandler);
-             return mock;
- 
-         } else {
diff --git a/build.gradle b/build.gradle
deleted file mode 100644
index 2a0f877..0000000
--- a/build.gradle
+++ /dev/null
@@ -1,26 +0,0 @@
-apply plugin: 'java'
-
-sourceSets {
-    main {
-        java {
-            srcDirs = [
-                'src/main/java',
-                'src/dx/java',
-                'src/mockito/java',
-            ]
-        }
-        resources {
-            srcDirs = ['src/mockito/resources']
-        }
-    }
-}
-
-jar {
-    baseName 'dexmaker'
-    classifier 'mockmaker'
-}
-
-dependencies {
-    compile getAndroidPrebuilt('10')
-    compile project(path: ':mockito', configuration: 'target')
-}
diff --git a/dexmaker-mockito-inline-dispatcher/build.gradle b/dexmaker-mockito-inline-dispatcher/build.gradle
index c9667c0..73a93f3 100644
--- a/dexmaker-mockito-inline-dispatcher/build.gradle
+++ b/dexmaker-mockito-inline-dispatcher/build.gradle
@@ -1,17 +1,18 @@
 apply plugin: 'com.android.application'
 
 android {
-    compileSdkVersion 25
-    buildToolsVersion "25.0.0"
-
-    lintOptions {
-        abortOnError false
-    }
+    compileSdkVersion 28
+    buildToolsVersion '28.0.3'
 
     defaultConfig {
-        applicationId "com.android.dexmaker.mockito.inline.dispatcher"
-        minSdkVersion 25
-        targetSdkVersion 25
+        applicationId 'com.android.dexmaker.mockito.inline.dispatcher'
+        minSdkVersion 28
+        targetSdkVersion 28
         versionName VERSION_NAME
     }
-}
\ No newline at end of file
+}
+
+repositories {
+    jcenter()
+    google()
+}
diff --git a/dexmaker-mockito-inline-extended-tests/build.gradle b/dexmaker-mockito-inline-extended-tests/build.gradle
new file mode 100644
index 0000000..fcff13c
--- /dev/null
+++ b/dexmaker-mockito-inline-extended-tests/build.gradle
@@ -0,0 +1,54 @@
+buildscript {
+    repositories {
+        maven {
+            url "https://plugins.gradle.org/m2/"
+        }
+    }
+    dependencies {
+        classpath "net.ltgt.gradle:gradle-errorprone-plugin:0.0.13"
+    }
+}
+
+apply plugin: "net.ltgt.errorprone"
+apply plugin: 'com.android.library'
+
+android {
+    compileSdkVersion 28
+    buildToolsVersion '28.0.3'
+
+    android {
+        lintOptions {
+            disable 'InvalidPackage'
+        }
+    }
+
+    defaultConfig {
+        minSdkVersion 28
+        targetSdkVersion 28
+        versionName VERSION_NAME
+
+        testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
+    }
+
+    compileOptions {
+        targetCompatibility 1.8
+        sourceCompatibility 1.8
+    }
+}
+
+repositories {
+    jcenter()
+    google()
+}
+
+dependencies {
+    implementation project(':dexmaker-mockito-inline-tests')
+    compileOnly project(':dexmaker-mockito-inline-extended')
+    androidTestImplementation project(':dexmaker-mockito-inline-extended')
+
+    implementation 'junit:junit:4.12'
+    implementation 'com.android.support.test:runner:1.0.2'
+    implementation 'com.android.support.test:rules:1.0.2'
+
+    api 'org.mockito:mockito-core:2.21.0', { exclude group: 'net.bytebuddy' }
+}
diff --git a/dexmaker-mockito-inline-extended-tests/src/main/AndroidManifest.xml b/dexmaker-mockito-inline-extended-tests/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..11f9513
--- /dev/null
+++ b/dexmaker-mockito-inline-extended-tests/src/main/AndroidManifest.xml
@@ -0,0 +1,9 @@
+<manifest package="com.android.dexmaker.mockito.inline.extended.tests"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <application android:debuggable="true"
+        tools:ignore="HardcodedDebugMode">
+        <activity android:name="com.android.dx.mockito.inline.extended.tests.EmptyActivity" />
+    </application>
+</manifest>
diff --git a/dexmaker-mockito-inline-extended-tests/src/main/java/com/android/dx/mockito/inline/extended/tests/EmptyActivity.java b/dexmaker-mockito-inline-extended-tests/src/main/java/com/android/dx/mockito/inline/extended/tests/EmptyActivity.java
new file mode 100644
index 0000000..77e1f5a
--- /dev/null
+++ b/dexmaker-mockito-inline-extended-tests/src/main/java/com/android/dx/mockito/inline/extended/tests/EmptyActivity.java
@@ -0,0 +1,26 @@
+/*
+ * 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.inline.extended.tests;
+
+import android.app.Activity;
+
+public class EmptyActivity extends Activity {
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+    }
+}
diff --git a/dexmaker-mockito-inline-extended-tests/src/main/java/com/android/dx/mockito/inline/extended/tests/MockInstanceUsingExtendedMockito.java b/dexmaker-mockito-inline-extended-tests/src/main/java/com/android/dx/mockito/inline/extended/tests/MockInstanceUsingExtendedMockito.java
new file mode 100644
index 0000000..c21bde5
--- /dev/null
+++ b/dexmaker-mockito-inline-extended-tests/src/main/java/com/android/dx/mockito/inline/extended/tests/MockInstanceUsingExtendedMockito.java
@@ -0,0 +1,97 @@
+/*
+ * 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.inline.extended.tests;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.dx.mockito.inline.extended.StaticInOrder;
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+
+import org.junit.Test;
+import org.mockito.Mock;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.eq;
+
+public class MockInstanceUsingExtendedMockito {
+    @Mock
+    private TestClass mockField;
+
+    public static class TestClass {
+        public String echo(String arg) {
+            return arg;
+        }
+    }
+
+    @Test
+    public void mockClass() throws Exception {
+        TestClass t = mock(TestClass.class);
+
+        assertNull(t.echo("mocked"));
+
+        when(t.echo(eq("stubbed"))).thenReturn("B");
+        assertEquals("B", t.echo("stubbed"));
+        verify(t).echo("mocked");
+        verify(t).echo("stubbed");
+    }
+
+    @Test
+    public void useMockitoSession() throws Exception {
+        StaticMockitoSession session = mockitoSession().initMocks(this).startMocking();
+        try {
+            assertNull(mockField.echo("mocked"));
+
+            when(mockField.echo(eq("stubbed"))).thenReturn("B");
+            assertEquals("B", mockField.echo("stubbed"));
+            verify(mockField).echo("mocked");
+            verify(mockField).echo("stubbed");
+        } finally {
+            session.finishMocking();
+        }
+    }
+
+    @Test
+    public void verifyInOrder() throws Exception {
+        TestClass t = mock(TestClass.class);
+
+        assertNull(t.echo("mocked"));
+
+        when(t.echo(eq("stubbed"))).thenReturn("B");
+        assertEquals("B", t.echo("stubbed"));
+
+        StaticInOrder inOrder = ExtendedMockito.inOrder(t);
+        inOrder.verify(t).echo("mocked");
+        inOrder.verify(t).echo("stubbed");
+    }
+
+    @Test
+    public void mockClassUsingDoReturn() throws Exception {
+        TestClass t = mock(TestClass.class);
+
+        assertNull(t.echo("mocked"));
+
+        doReturn("B").when(t).echo(eq("stubbed"));
+        assertEquals("B", t.echo("stubbed"));
+        verify(t).echo("mocked");
+        verify(t).echo("stubbed");
+    }
+}
diff --git a/dexmaker-mockito-inline-extended-tests/src/main/java/com/android/dx/mockito/inline/extended/tests/MockStatic.java b/dexmaker-mockito-inline-extended-tests/src/main/java/com/android/dx/mockito/inline/extended/tests/MockStatic.java
new file mode 100644
index 0000000..95a9baa
--- /dev/null
+++ b/dexmaker-mockito-inline-extended-tests/src/main/java/com/android/dx/mockito/inline/extended/tests/MockStatic.java
@@ -0,0 +1,432 @@
+/*
+ * 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.mockito.inline.extended.tests;
+
+import static android.provider.Settings.Global.DEVICE_NAME;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.clearInvocations;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockingDetails;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.staticMockMarker;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyZeroInteractions;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+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.any;
+import static org.mockito.ArgumentMatchers.eq;
+
+import android.content.ContentResolver;
+import android.provider.Settings;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Test;
+import org.mockito.MockingDetails;
+import org.mockito.MockitoSession;
+import org.mockito.exceptions.misusing.MissingMethodInvocationException;
+import org.mockito.quality.Strictness;
+
+public class MockStatic {
+    private static class SuperClass {
+        final String returnA() {
+            return "superA";
+        }
+
+        static String returnB() {
+            return "superB";
+        }
+
+        static String returnC() {
+            return "superC";
+        }
+    }
+
+    private static final class SubClass extends SuperClass {
+        static String recorded = null;
+
+        static String returnC() {
+            return "subC";
+        }
+
+        static final String record(String toRecord) {
+            recorded = toRecord;
+            return "record";
+        }
+    }
+
+    private static class NoDefaultConstructorClass {
+        private static int mLastId;
+
+        NoDefaultConstructorClass(int id) {
+            mLastId = id;
+        }
+
+        static int getLastId() {
+            return mLastId;
+        }
+    }
+
+    @Test
+    public void spyStatic() throws Exception {
+        ContentResolver resolver = InstrumentationRegistry.getTargetContext().getContentResolver();
+        String deviceName = Settings.Global.getString(resolver, DEVICE_NAME);
+
+        MockitoSession session = mockitoSession().spyStatic(Settings.Global.class).startMocking();
+        try {
+            // Cannot call when(Settings.getString(any(ContentResolver.class), eq("...")))
+            // as any(ContentResolver.class) returns null which makes getString fail. Hence need to
+            // use less lambda API
+            doReturn("23").when(() -> Settings.Global.getString(any
+                    (ContentResolver.class), eq("twenty three")));
+
+            doReturn(42).when(() -> Settings.Global.getInt(any
+                    (ContentResolver.class), eq("fourty two")));
+
+            // Make sure behavior is changed
+            assertEquals("23", Settings.Global.getString(resolver, "twenty three"));
+            assertEquals(42, Settings.Global.getInt(resolver, "fourty two"));
+
+            // Make sure non-mocked methods work as before
+            assertEquals(deviceName, Settings.Global.getString(resolver, DEVICE_NAME));
+        } finally {
+            session.finishMocking();
+        }
+    }
+
+    @Test
+    public void spyStaticOnObjectWithNoDefaultConstructor() throws Exception {
+        new NoDefaultConstructorClass(23);
+
+        MockitoSession session = mockitoSession().spyStatic(NoDefaultConstructorClass.class)
+                .startMocking();
+        try {
+            // Starting the spying hasn't change the static state of the class.
+            assertEquals(23, NoDefaultConstructorClass.getLastId());
+
+            when(NoDefaultConstructorClass.getLastId()).thenReturn(42);
+            assertEquals(42, NoDefaultConstructorClass.getLastId());
+        } finally {
+            session.finishMocking();
+        }
+    }
+
+    @Test
+    public void mockStatic() throws Exception {
+        ContentResolver resolver = InstrumentationRegistry.getTargetContext().getContentResolver();
+        String deviceName = Settings.Global.getString(resolver, DEVICE_NAME);
+
+        MockitoSession session = mockitoSession().mockStatic(Settings.Global.class).startMocking();
+        try {
+            // By default all static methods of the mocked class should return null/0/false
+            assertNull(Settings.Global.getString(resolver, DEVICE_NAME));
+
+            when(Settings.Global.getString(any(ContentResolver.class), eq(DEVICE_NAME)))
+                    .thenReturn("This is a test");
+
+            // Make sure behavior is changed
+            assertEquals("This is a test", Settings.Global.getString(resolver, DEVICE_NAME));
+        } finally {
+            session.finishMocking();
+        }
+
+        // Once the mocking is removed, the behavior should be back to normal
+        assertEquals(deviceName, Settings.Global.getString(resolver, DEVICE_NAME));
+    }
+
+    @Test
+    public void mockOverriddenStaticMethod() throws Exception {
+        MockitoSession session = mockitoSession().mockStatic(SubClass.class).startMocking();
+        try {
+            // By default all static methods of the mocked class should return the default answers
+            assertNull(SubClass.returnB());
+            assertNull(SubClass.returnC());
+
+            // Super class is not mocked
+            assertEquals("superB", SuperClass.returnB());
+            assertEquals("superC", SuperClass.returnC());
+
+            when(SubClass.returnB()).thenReturn("fakeB");
+            when(SubClass.returnC()).thenReturn("fakeC");
+
+            // Make sure behavior is changed
+            assertEquals("fakeB", SubClass.returnB());
+            assertEquals("fakeC", SubClass.returnC());
+
+            // Super class should not be affected
+            assertEquals("superB", SuperClass.returnB());
+            assertEquals("superC", SuperClass.returnC());
+        } finally {
+            session.finishMocking();
+        }
+
+        // Mocking should be stopped
+        assertEquals("superB", SubClass.returnB());
+        assertEquals("subC", SubClass.returnC());
+    }
+
+    @Test
+    public void mockSuperMethod() throws Exception {
+        MockitoSession session = mockitoSession().mockStatic(SuperClass.class).startMocking();
+        try {
+            // By default all static methods of the mocked class should return the default answers
+            assertNull(SuperClass.returnB());
+            assertNull(SuperClass.returnC());
+
+            // Sub class should not be affected
+            assertEquals("superB", SubClass.returnB());
+            assertEquals("subC", SubClass.returnC());
+
+            when(SuperClass.returnB()).thenReturn("fakeB");
+            when(SuperClass.returnC()).thenReturn("fakeC");
+
+            // Make sure behavior is changed
+            assertEquals("fakeB", SuperClass.returnB());
+            assertEquals("fakeC", SuperClass.returnC());
+
+            // Sub class should not be affected
+            assertEquals("superB", SubClass.returnB());
+            assertEquals("subC", SubClass.returnC());
+        } finally {
+            session.finishMocking();
+        }
+
+        // Mocking should be stopped
+        assertEquals("superB", SuperClass.returnB());
+        assertEquals("superC", SuperClass.returnC());
+    }
+
+    @Test(expected = MissingMethodInvocationException.class)
+    public void nonMockedTest() throws Exception {
+        when(SuperClass.returnB()).thenReturn("fakeB");
+    }
+
+    @Test
+    public void resetMock() throws Exception {
+        MockitoSession session = mockitoSession().mockStatic(SuperClass.class).startMocking();
+        try {
+            assertNull(SuperClass.returnB());
+
+            when(SuperClass.returnB()).thenReturn("fakeB");
+            assertEquals("fakeB", SuperClass.returnB());
+
+            reset(staticMockMarker(SuperClass.class));
+            assertNull(SuperClass.returnB());
+        } finally {
+            session.finishMocking();
+        }
+    }
+
+    @Test
+    public void resetSpy() throws Exception {
+        MockitoSession session = mockitoSession().spyStatic(SuperClass.class).startMocking();
+        try {
+            assertEquals("superB", SuperClass.returnB());
+
+            when(SuperClass.returnB()).thenReturn("fakeB");
+            assertEquals("fakeB", SuperClass.returnB());
+
+            reset(staticMockMarker(SuperClass.class));
+            assertEquals("superB", SuperClass.returnB());
+        } finally {
+            session.finishMocking();
+        }
+    }
+
+    @Test
+    public void staticMockingIsSeparateFromNonStaticMocking() throws Exception {
+        SuperClass objA = new SuperClass();
+        SuperClass objB;
+
+        MockitoSession session = mockitoSession().mockStatic(SuperClass.class).startMocking();
+        try {
+            assertNull(SuperClass.returnB());
+            assertNull(objA.returnB());
+
+            objB = mock(SuperClass.class);
+
+            assertEquals("superA", objA.returnA());
+
+            // Any kind of static method method call should be mocked
+            assertNull(objB.returnA());
+
+            assertNull(SuperClass.returnB());
+            assertNull(objA.returnB());
+            assertNull(objB.returnB());
+        } finally {
+            session.finishMocking();
+        }
+
+        assertEquals("superA", objA.returnA());
+        assertNull(objB.returnA());
+
+        // Any kind of static method method call should _not_ be mocked
+        assertEquals("superB", SuperClass.returnB());
+        assertEquals("superB", objA.returnB());
+        assertEquals("superB", objB.returnB());
+    }
+
+    @Test
+    public void mockWithTwoClasses() throws Exception {
+        MockitoSession session = mockitoSession().mockStatic(SuperClass.class)
+                .mockStatic(SubClass.class).startMocking();
+        try {
+            when(SuperClass.returnB()).thenReturn("fakeB");
+            assertEquals("fakeB", SuperClass.returnB());
+
+            when(SubClass.returnC()).thenReturn("fakeC");
+            assertEquals("fakeC", SubClass.returnC());
+        } finally {
+            session.finishMocking();
+        }
+    }
+
+    @Test
+    public void doReturnMockWithTwoClasses() throws Exception {
+        MockitoSession session = mockitoSession().mockStatic(SuperClass.class)
+                .mockStatic(SubClass.class).startMocking();
+        try {
+            doReturn("fakeB").when(SuperClass::returnB);
+            assertEquals("fakeB", SuperClass.returnB());
+
+            doReturn("fakeD").when(() -> SubClass.record("test"));
+            assertEquals("fakeD", SubClass.record("test"));
+        } finally {
+            session.finishMocking();
+        }
+    }
+
+    @Test
+    public void doReturnTwice() throws Exception {
+        MockitoSession session = mockitoSession().mockStatic(SuperClass.class).startMocking();
+        try {
+            doReturn("fakeB").doReturn("fakeB2").when(SuperClass::returnB);
+            assertEquals("fakeB", SuperClass.returnB());
+            assertEquals("fakeB2", SuperClass.returnB());
+        } finally {
+            session.finishMocking();
+        }
+    }
+
+    @Test
+    public void doReturnSpyHasNoSideEffect() throws Exception {
+        MockitoSession session = mockitoSession().spyStatic(SubClass.class).startMocking();
+        try {
+            SubClass.recorded = null;
+            SubClass.record("no sideeffect");
+            assertEquals("no sideeffect", SubClass.recorded);
+
+            doReturn("faceRecord").when(() -> SubClass.record(eq("test")));
+            // Verify that there was no side effect as the lambda gets intercepted
+            assertEquals("no sideeffect", SubClass.recorded);
+
+            assertEquals("faceRecord", SubClass.record("test"));
+            // Verify that there was no side effect as the method is stubbed
+            assertEquals("no sideeffect", SubClass.recorded);
+        } finally {
+            session.finishMocking();
+        }
+    }
+
+    @Test
+    public void onlyOneMethodCallDuringStubbing() throws Exception {
+        MockitoSession session = mockitoSession().strictness(Strictness.LENIENT)
+                .spyStatic(SuperClass.class).startMocking();
+        try {
+            try {
+                doReturn("").when(() -> {
+                    SuperClass.returnB();
+                    SuperClass.returnC();
+                });
+                fail();
+            } catch (IllegalArgumentException e) {
+                assertTrue(e.getMessage(), e.getMessage().contains("returnB"));
+                assertTrue(e.getMessage(), e.getMessage().contains("returnC"));
+
+                assertFalse(e.getMessage(), e.getMessage().contains("returnA"));
+            }
+        } finally {
+            session.finishMocking();
+        }
+    }
+
+    @Test
+    public void atLeastOneMethodCallDuringStubbing() throws Exception {
+        Exception atLeastOneMethodCallException = null;
+
+        try {
+            MockitoSession session = mockitoSession().spyStatic(SuperClass.class).startMocking();
+            try {
+                try {
+                    doReturn("").when(() -> {
+                    });
+                    fail();
+                } catch (IllegalArgumentException expected) {
+                    atLeastOneMethodCallException = expected;
+                }
+            } finally {
+                session.finishMocking();
+            }
+        } catch (Throwable ignored) {
+            // We don't want to test exceptions form MockitoSession
+        }
+
+        assertNotNull(atLeastOneMethodCallException);
+    }
+
+    @Test
+    public void clearInvocationsRemovedInvocations() throws Exception {
+        MockitoSession session = mockitoSession().mockStatic(SuperClass.class).startMocking();
+        try {
+            SuperClass.returnB();
+            clearInvocations(staticMockMarker(SuperClass.class));
+            verifyZeroInteractions(staticMockMarker(SuperClass.class));
+        } finally {
+            session.finishMocking();
+        }
+    }
+
+    @Test
+    public void verifyMockingDetails() throws Exception {
+        MockitoSession session = mockitoSession().mockStatic(SuperClass.class)
+                .spyStatic(SubClass.class).startMocking();
+        try {
+            when(SuperClass.returnB()).thenReturn("fakeB");
+            SuperClass.returnB();
+            SuperClass.returnC();
+
+            MockingDetails superClassDetails = mockingDetails(staticMockMarker(SuperClass.class));
+            assertTrue(superClassDetails.isMock());
+            assertFalse(superClassDetails.isSpy());
+            assertEquals(2, superClassDetails.getInvocations().size());
+            assertEquals(1, superClassDetails.getStubbings().size());
+
+            MockingDetails subClassDetails = mockingDetails(staticMockMarker(SubClass.class));
+            assertTrue(subClassDetails.isMock());
+            assertTrue(subClassDetails.isSpy());
+        } finally {
+            session.finishMocking();
+        }
+    }
+}
diff --git a/dexmaker-mockito-inline-extended-tests/src/main/java/com/android/dx/mockito/inline/extended/tests/SpyOn.java b/dexmaker-mockito-inline-extended-tests/src/main/java/com/android/dx/mockito/inline/extended/tests/SpyOn.java
new file mode 100644
index 0000000..0604e9a
--- /dev/null
+++ b/dexmaker-mockito-inline-extended-tests/src/main/java/com/android/dx/mockito/inline/extended/tests/SpyOn.java
@@ -0,0 +1,117 @@
+/*
+ * 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.inline.extended.tests;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SpyOn {
+    @Rule
+    public ActivityTestRule<EmptyActivity> activityRule =
+            new ActivityTestRule<>(EmptyActivity.class);
+
+    public class TestClass {
+        Object field;
+
+        public String echo(String in) {
+            return in;
+        }
+    }
+
+    @Test
+    public void spyOnLocalClass() {
+        TestClass t = new TestClass();
+        assertEquals("one", t.echo("one"));
+
+        spyOn(t);
+        assertEquals("two", t.echo("two"));
+        verify(t).echo("two");
+
+        when(t.echo("three")).thenReturn("not three");
+        assertEquals("not three", t.echo("three"));
+        verify(t).echo("three");
+    }
+
+    @Test
+    public void localFieldStaysTheSame() {
+        TestClass t = new TestClass();
+
+        Object marker = mock(Object.class);
+        t.field = marker;
+
+        spyOn(t);
+        assertSame(marker, t.field);
+    }
+
+    @Test
+    public void spiesAreUsuallyClones() {
+        TestClass original = new TestClass();
+
+        Object marker = new Object();
+        original.field = marker;
+
+        TestClass spy = spy(original);
+        assertSame(marker, spy.field);
+
+        assertNotSame(original, spy);
+    }
+
+    @Test
+    public void spyOnActivity() throws Exception {
+        EmptyActivity a = activityRule.getActivity();
+        spyOn(a);
+
+        // Intercept a#onDestroy(). The first time this is called isDestroyed[0] is set to true,
+        // the second time it is called, it calls the real method.
+        boolean isDestroyed[] = new boolean[]{false};
+        doAnswer((inv) -> {
+            synchronized (isDestroyed) {
+                isDestroyed[0] = true;
+                isDestroyed.notifyAll();
+            }
+
+            // Call a second time to call super method before returning. Android requires onDestroy
+            // to always call it's super-method.
+            a.onDestroy();
+            return null;
+        }).doCallRealMethod().when(a).onDestroy();
+
+        activityRule.finishActivity();
+
+        synchronized (isDestroyed) {
+            while (!isDestroyed[0]) {
+                isDestroyed.wait();
+            }
+        }
+    }
+}
diff --git a/dexmaker-mockito-inline-extended-tests/src/main/java/com/android/dx/mockito/inline/extended/tests/StaticMockitoSession.java b/dexmaker-mockito-inline-extended-tests/src/main/java/com/android/dx/mockito/inline/extended/tests/StaticMockitoSession.java
new file mode 100644
index 0000000..7c7941b
--- /dev/null
+++ b/dexmaker-mockito-inline-extended-tests/src/main/java/com/android/dx/mockito/inline/extended/tests/StaticMockitoSession.java
@@ -0,0 +1,64 @@
+/*
+ * 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.inline.extended.tests;
+
+import android.content.ContentResolver;
+import android.provider.Settings;
+
+import org.junit.Test;
+import org.mockito.MockitoSession;
+import org.mockito.exceptions.misusing.UnnecessaryStubbingException;
+import org.mockito.quality.Strictness;
+
+import static android.provider.Settings.Global.DEVICE_NAME;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+
+public class StaticMockitoSession {
+    @Test
+    public void strictUnnecessaryStubbing() throws Exception {
+        MockitoSession session = mockitoSession().spyStatic(Settings.Global.class).startMocking();
+
+        // Set up unnecessary stubbing
+        doReturn("23").when(() -> Settings.Global.getString(any
+                (ContentResolver.class), eq(DEVICE_NAME)));
+
+        try {
+            session.finishMocking();
+            fail();
+        } catch (UnnecessaryStubbingException e) {
+            assertTrue("\"" + e.getMessage() + "\" does not contain 'Settings$Global.getString'",
+                    e.getMessage().contains("Settings$Global.getString"));
+        }
+    }
+
+    @Test
+    public void lenientUnnecessaryStubbing() throws Exception {
+        MockitoSession session = mockitoSession().strictness(Strictness.LENIENT)
+                .spyStatic(Settings.Global.class).startMocking();
+
+        // Set up unnecessary stubbing
+        doReturn("23").when(() -> Settings.Global.getString(any
+                (ContentResolver.class), eq(DEVICE_NAME)));
+
+        session.finishMocking();
+    }
+}
diff --git a/dexmaker-mockito-inline-extended-tests/src/main/java/com/android/dx/mockito/inline/extended/tests/StaticMockitoSessionVsMockitoJUnitRunner.java b/dexmaker-mockito-inline-extended-tests/src/main/java/com/android/dx/mockito/inline/extended/tests/StaticMockitoSessionVsMockitoJUnitRunner.java
new file mode 100644
index 0000000..fe75755
--- /dev/null
+++ b/dexmaker-mockito-inline-extended-tests/src/main/java/com/android/dx/mockito/inline/extended/tests/StaticMockitoSessionVsMockitoJUnitRunner.java
@@ -0,0 +1,29 @@
+/*
+ * 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.inline.extended.tests;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class StaticMockitoSessionVsMockitoJUnitRunner {
+    @Test
+    public void simpleStubbing() throws Exception {
+        (new MockStatic()).spyStatic();
+    }
+}
diff --git a/dexmaker-mockito-inline-extended-tests/src/main/java/com/android/dx/mockito/inline/extended/tests/Stress.java b/dexmaker-mockito-inline-extended-tests/src/main/java/com/android/dx/mockito/inline/extended/tests/Stress.java
new file mode 100644
index 0000000..6d449bb
--- /dev/null
+++ b/dexmaker-mockito-inline-extended-tests/src/main/java/com/android/dx/mockito/inline/extended/tests/Stress.java
@@ -0,0 +1,87 @@
+/*
+ * 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.inline.extended.tests;
+
+import android.util.Log;
+
+import org.junit.Test;
+import org.mockito.MockitoSession;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.staticMockMarker;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.when;
+
+
+public class Stress {
+    private static final String LOG_TAG = Stress.class.getSimpleName();
+
+    private static class SuperClass {
+        static String returnB() {
+            return "superB";
+        }
+
+        final static String returnD() {
+            return "superD";
+        }
+    }
+
+    @Test
+    public void stressFinalStaticMethod() throws Exception {
+        MockitoSession session = mockitoSession().mockStatic(SuperClass.class).startMocking();
+        try {
+            assertNull(SuperClass.returnD());
+
+            for (int i = 0; i < 1000; i++) {
+                when(SuperClass.returnD()).thenReturn("fakeD");
+                assertEquals("fakeD", SuperClass.returnD());
+
+                reset(staticMockMarker(SuperClass.class));
+                assertNull(SuperClass.returnD());
+
+                if (i % 100 == 0) {
+                    Log.i(LOG_TAG, "Ran " + i + " tests");
+                }
+            }
+        } finally {
+            session.finishMocking();
+        }
+    }
+
+    @Test
+    public void stressStaticMethod() throws Exception {
+        MockitoSession session = mockitoSession().mockStatic(SuperClass.class).startMocking();
+        try {
+            assertNull(SuperClass.returnB());
+
+            for (int i = 0; i < 10; i++) {
+                when(SuperClass.returnB()).thenReturn("fakeB");
+                assertEquals("fakeB", SuperClass.returnB());
+
+                reset(staticMockMarker(SuperClass.class));
+                assertNull(SuperClass.returnB());
+
+                Log.i(LOG_TAG, "Ran " + i + " tests");
+            }
+        } finally {
+            session.finishMocking();
+        }
+    }
+
+}
diff --git a/dexmaker-mockito-inline-extended-tests/src/main/java/com/android/dx/mockito/inline/extended/tests/VerifyStatic.java b/dexmaker-mockito-inline-extended-tests/src/main/java/com/android/dx/mockito/inline/extended/tests/VerifyStatic.java
new file mode 100644
index 0000000..afb1439
--- /dev/null
+++ b/dexmaker-mockito-inline-extended-tests/src/main/java/com/android/dx/mockito/inline/extended/tests/VerifyStatic.java
@@ -0,0 +1,217 @@
+/*
+ * 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.inline.extended.tests;
+
+import com.android.dx.mockito.inline.extended.StaticInOrder;
+
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.MockitoSession;
+import org.mockito.exceptions.verification.NoInteractionsWanted;
+import org.mockito.exceptions.verification.VerificationInOrderFailure;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.ignoreStubs;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.staticMockMarker;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyZeroInteractions;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.eq;
+
+public class VerifyStatic {
+    @Test
+    public void verifyMockedStringMethod() throws Exception {
+        MockitoSession session = mockitoSession().mockStatic(EchoClass.class).startMocking();
+        try {
+            assertNull(EchoClass.echo("marco!"));
+
+            ArgumentCaptor<String> echoCaptor = ArgumentCaptor.forClass(String.class);
+            verify(() -> {return EchoClass.echo(echoCaptor.capture());});
+            assertEquals("marco!", echoCaptor.getValue());
+
+            verifyNoMoreInteractions(staticMockMarker(EchoClass.class));
+        } finally {
+            session.finishMocking();
+        }
+    }
+
+    @Test
+    public void verifyMockedVoidMethod() throws Exception {
+        MockitoSession session = mockitoSession().mockStatic(ConsumeClass.class).startMocking();
+        try {
+            ConsumeClass.consume("donut");
+
+            ArgumentCaptor<String> yumCaptor = ArgumentCaptor.forClass(String.class);
+            verify(() -> ConsumeClass.consume(yumCaptor.capture()));
+
+            verifyNoMoreInteractions(staticMockMarker(ConsumeClass.class));
+        } finally {
+            session.finishMocking();
+        }
+    }
+
+    @Test
+    public void verifyWithTwoMocks() throws Exception {
+        MockitoSession session = mockitoSession().mockStatic(EchoClass.class)
+                .mockStatic(ConsumeClass.class).startMocking();
+        try {
+            ConsumeClass.consume("donut");
+            assertNull(EchoClass.echo("marco!"));
+
+            ArgumentCaptor<String> yumCaptor = ArgumentCaptor.forClass(String.class);
+            verify(() -> ConsumeClass.consume(yumCaptor.capture()));
+
+            ArgumentCaptor<String> echoCaptor = ArgumentCaptor.forClass(String.class);
+            verify(() -> {return EchoClass.echo(echoCaptor.capture());});
+            assertEquals("marco!", echoCaptor.getValue());
+
+            verifyNoMoreInteractions(staticMockMarker(ConsumeClass.class));
+        } finally {
+            session.finishMocking();
+        }
+    }
+
+    @Test
+    public void verifySpiedStringMethod() throws Exception {
+        MockitoSession session = mockitoSession().spyStatic(EchoClass.class).startMocking();
+        try {
+            assertEquals("marco!", EchoClass.echo("marco!"));
+
+            ArgumentCaptor<String> echoCaptor = ArgumentCaptor.forClass(String.class);
+            verify(() -> {return EchoClass.echo(echoCaptor.capture());});
+            assertEquals("marco!", echoCaptor.getValue());
+
+            verifyNoMoreInteractions(staticMockMarker(EchoClass.class));
+        } finally {
+            session.finishMocking();
+        }
+    }
+
+    @Test
+    public void verifyInOrder() throws Exception {
+        MockitoSession session = mockitoSession().mockStatic(EchoClass.class).mockStatic
+                (ConsumeClass.class).startMocking();
+        try {
+            EchoClass.echo("marco!");
+            ConsumeClass.consume("donuts");
+            ConsumeClass.consume("nougat");
+            EchoClass.echo("polo");
+
+            StaticInOrder echoInOrder = inOrder(staticMockMarker(EchoClass.class));
+            echoInOrder.verify(() -> EchoClass.echo(eq("marco!")));
+            echoInOrder.verify(() -> EchoClass.echo(eq("polo")));
+            echoInOrder.verifyNoMoreInteractions();
+
+            StaticInOrder consumeInOrder = inOrder(staticMockMarker(ConsumeClass.class));
+            consumeInOrder.verify(() -> ConsumeClass.consume(eq("donuts")));
+            consumeInOrder.verify(() -> ConsumeClass.consume(eq("nougat")));
+            consumeInOrder.verifyNoMoreInteractions();
+
+            StaticInOrder combinedInOrder = inOrder(staticMockMarker(EchoClass.class,
+                    ConsumeClass.class));
+            combinedInOrder.verify(() -> EchoClass.echo(eq("marco!")));
+            combinedInOrder.verify(() -> ConsumeClass.consume(eq("donuts")));
+            combinedInOrder.verify(() -> ConsumeClass.consume(eq("nougat")));
+            combinedInOrder.verify(() -> EchoClass.echo(eq("polo")));
+            combinedInOrder.verifyNoMoreInteractions();
+        } finally {
+            session.finishMocking();
+        }
+    }
+
+    @Test(expected = VerificationInOrderFailure.class)
+    public void verifyBadOrder() throws Exception {
+        MockitoSession session = mockitoSession().mockStatic(EchoClass.class).startMocking();
+        try {
+            EchoClass.echo("marco!");
+            EchoClass.echo("polo");
+
+            StaticInOrder echoInOrder = inOrder(staticMockMarker(EchoClass.class));
+            echoInOrder.verify(() -> EchoClass.echo(eq("polo")));
+            echoInOrder.verify(() -> EchoClass.echo(eq("marco!")));
+        } finally {
+            session.finishMocking();
+        }
+    }
+
+    @Test
+    public void verifyBadMatcher() throws Exception {
+        MockitoSession session = mockitoSession().mockStatic(EchoClass.class).startMocking();
+        try {
+            EchoClass.echo("marco!");
+            EchoClass.echo("polo");
+
+            StaticInOrder echoInOrder = inOrder(staticMockMarker(EchoClass.class));
+            echoInOrder.verify(() -> EchoClass.echo(eq("marco!")));
+
+            try {
+                echoInOrder.verify(() -> EchoClass.echo(eq("badMarker")));
+                fail();
+            } catch (VerificationInOrderFailure e) {
+                assertTrue(e.getMessage(), e.getMessage().contains("badMarker"));
+            }
+        } finally {
+            session.finishMocking();
+        }
+    }
+
+    @Test(expected = NoInteractionsWanted.class)
+    public void zeroInvocationsThrowsIfThereWasAnInvocation() throws Exception {
+        MockitoSession session = mockitoSession().mockStatic(EchoClass.class).startMocking();
+        try {
+            EchoClass.echo("marco!");
+            verifyZeroInteractions(staticMockMarker(EchoClass.class));
+            fail();
+        } finally {
+            session.finishMocking();
+        }
+    }
+
+    @Test
+    public void verifyWithIgnoreStubs() throws Exception {
+        MockitoSession session = mockitoSession().spyStatic(EchoClass.class).startMocking();
+        try {
+            // 'ignoreStubs' only ignore stubs
+            when(EchoClass.echo("marco!")).thenReturn("polo");
+            assertEquals("polo", EchoClass.echo("marco!"));
+            assertEquals("echo", EchoClass.echo("echo"));
+
+            verify(() -> {return EchoClass.echo(eq("echo"));});
+            verifyNoMoreInteractions(ignoreStubs(staticMockMarker(EchoClass.class)));
+        } finally {
+            session.finishMocking();
+        }
+    }
+
+    private static class EchoClass {
+        static final String echo(String echo) {
+            return echo;
+        }
+    }
+
+    private static class ConsumeClass {
+        static final void consume(String yum) {
+            // empty
+        }
+    }
+}
diff --git a/dexmaker-mockito-inline-extended/CMakeLists.txt b/dexmaker-mockito-inline-extended/CMakeLists.txt
new file mode 100644
index 0000000..cec4ec0
--- /dev/null
+++ b/dexmaker-mockito-inline-extended/CMakeLists.txt
@@ -0,0 +1,33 @@
+cmake_minimum_required(VERSION 3.4.1)
+
+set(slicer_sources
+    ../dexmaker-mockito-inline/external/slicer/bytecode_encoder.cc
+    ../dexmaker-mockito-inline/external/slicer/code_ir.cc
+    ../dexmaker-mockito-inline/external/slicer/common.cc
+    ../dexmaker-mockito-inline/external/slicer/control_flow_graph.cc
+    ../dexmaker-mockito-inline/external/slicer/debuginfo_encoder.cc
+    ../dexmaker-mockito-inline/external/slicer/dex_bytecode.cc
+    ../dexmaker-mockito-inline/external/slicer/dex_format.cc
+    ../dexmaker-mockito-inline/external/slicer/dex_ir_builder.cc
+    ../dexmaker-mockito-inline/external/slicer/dex_ir.cc
+    ../dexmaker-mockito-inline/external/slicer/dex_utf8.cc
+    ../dexmaker-mockito-inline/external/slicer/instrumentation.cc
+    ../dexmaker-mockito-inline/external/slicer/reader.cc
+    ../dexmaker-mockito-inline/external/slicer/tryblocks_encoder.cc
+    ../dexmaker-mockito-inline/external/slicer/writer.cc)
+
+add_library(slicer
+            STATIC
+            ${slicer_sources})
+
+include_directories(../dexmaker-mockito-inline/external/jdk ../dexmaker-mockito-inline/external/slicer/export/)
+
+target_link_libraries(slicer z)
+
+add_library(staticjvmtiagent
+            SHARED
+            src/main/jni/staticjvmtiagent/agent.cc)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DANDROID_STL=c++_shared -frtti -Wall -Werror -Wno-unused-parameter -Wno-shift-count-overflow -Wno-error=non-virtual-dtor -Wno-sign-compare -Wno-switch -Wno-missing-braces")
+
+target_link_libraries(staticjvmtiagent slicer)
diff --git a/dexmaker-mockito-inline-extended/build.gradle b/dexmaker-mockito-inline-extended/build.gradle
new file mode 100644
index 0000000..3234e33
--- /dev/null
+++ b/dexmaker-mockito-inline-extended/build.gradle
@@ -0,0 +1,123 @@
+buildscript {
+    repositories {
+        maven {
+            url "https://plugins.gradle.org/m2/"
+        }
+    }
+    dependencies {
+        classpath "net.ltgt.gradle:gradle-errorprone-plugin:0.0.13"
+    }
+}
+
+apply plugin: "net.ltgt.errorprone"
+apply plugin: 'com.android.library'
+apply plugin: 'maven-publish'
+apply plugin: 'ivy-publish'
+apply plugin: 'com.jfrog.artifactory'
+
+version = VERSION_NAME
+
+android {
+    compileSdkVersion 28
+    buildToolsVersion '28.0.3'
+
+    android {
+        lintOptions {
+            disable 'InvalidPackage'
+            warning 'NewApi'
+        }
+    }
+
+    defaultConfig {
+        minSdkVersion 1
+        targetSdkVersion 28
+        versionName VERSION_NAME
+    }
+
+    externalNativeBuild {
+        cmake {
+            path 'CMakeLists.txt'
+        }
+    }
+
+    compileOptions {
+        targetCompatibility 1.8
+        sourceCompatibility 1.8
+    }
+}
+
+tasks.withType(JavaCompile) {
+    options.compilerArgs += ["-Xep:StringSplitter:OFF"]
+}
+
+task sourcesJar(type: Jar) {
+    classifier = 'sources'
+    from android.sourceSets.main.java.srcDirs
+}
+
+task javadoc(type: Javadoc) {
+    source = android.sourceSets.main.java.srcDirs
+    classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
+    failOnError false
+}
+
+task javadocJar(type: Jar, dependsOn: javadoc) {
+    classifier = 'javadoc'
+    from javadoc.destinationDir
+}
+
+publishing {
+    publications {
+        ivyLib(IvyPublication) {
+            from new org.gradle.api.internal.java.JavaLibrary(new org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact(project.getName(), 'aar', 'aar', null, new Date(), new File("$buildDir/outputs/aar/${project.getName()}-release.aar"), assemble), project.configurations.implementation.getAllDependencies())
+            artifact sourcesJar
+            artifact javadocJar
+        }
+
+        lib(MavenPublication) {
+            from new org.gradle.api.internal.java.JavaLibrary(new org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact(project.getName(), 'aar', 'aar', null, new Date(), new File("$buildDir/outputs/aar/${project.getName()}-release.aar"), assemble), project.configurations.implementation.getAllDependencies())
+
+            artifact sourcesJar
+            artifact javadocJar
+
+            pom.withXml {
+                asNode().children().last() + {
+                    resolveStrategy = Closure.DELEGATE_FIRST
+                    description = 'Extension of the Mockito Inline API to allow mocking static methods on the Android Dalvik VM'
+                    url 'https://github.com/linkedin/dexmaker'
+                    scm {
+                        url 'https://github.com/linkedin/dexmaker'
+                        connection 'scm:git:git://github.com/linkedin/dexmaker.git'
+                        developerConnection 'https://github.com/linkedin/dexmaker.git'
+                    }
+                    licenses {
+                        license {
+                            name 'The Apache Software License, Version 2.0'
+                            url 'http://www.apache.org/license/LICENSE-2.0.txt'
+                            distribution 'repo'
+                        }
+                    }
+
+                    developers {
+                        developer {
+                            id 'com.linkedin'
+                            name 'LinkedIn Corp'
+                            email ''
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+repositories {
+    jcenter()
+    google()
+}
+
+dependencies {
+    implementation project(':dexmaker-mockito-inline')
+
+    implementation 'org.mockito:mockito-core:2.21.0', { exclude group: 'net.bytebuddy' }
+}
diff --git a/dexmaker-mockito-inline-extended/src/main/AndroidManifest.xml b/dexmaker-mockito-inline-extended/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a521602
--- /dev/null
+++ b/dexmaker-mockito-inline-extended/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.dx.mockito.inline.extended" />
diff --git a/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/InlineStaticMockMaker.java b/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/InlineStaticMockMaker.java
new file mode 100644
index 0000000..f764e07
--- /dev/null
+++ b/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/InlineStaticMockMaker.java
@@ -0,0 +1,234 @@
+/*
+ * 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.inline;
+
+import android.os.Build;
+
+import org.mockito.Mockito;
+import org.mockito.creation.instance.Instantiator;
+import org.mockito.exceptions.base.MockitoException;
+import org.mockito.invocation.MockHandler;
+import org.mockito.mock.MockCreationSettings;
+import org.mockito.plugins.InstantiatorProvider2;
+import org.mockito.plugins.MockMaker;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.BiConsumer;
+
+/**
+ * Creates mock markers and adds stubbing hooks to static method
+ *
+ * <p>This is done by transforming the byte code of the classes to add method entry hooks.
+ */
+public final class InlineStaticMockMaker implements MockMaker {
+    /**
+     * {@link StaticJvmtiAgent} set up during one time init
+     */
+    private static final StaticJvmtiAgent AGENT;
+
+    /**
+     * Error  during one time init or {@code null} if init was successful
+     */
+    private static final Throwable INITIALIZATION_ERROR;
+    public static ThreadLocal<Class> mockingInProgressClass = new ThreadLocal<>();
+    public static ThreadLocal<BiConsumer<Class<?>, Method>> onMethodCallDuringStubbing
+            = new ThreadLocal<>();
+    public static ThreadLocal<BiConsumer<Class<?>, Method>> onMethodCallDuringVerification
+            = new ThreadLocal<>();
+
+    /*
+     * One time setup to allow the system to mocking via this mock maker.
+     */
+    static {
+        StaticJvmtiAgent agent;
+        Throwable initializationError = null;
+
+        try {
+            try {
+                agent = new StaticJvmtiAgent();
+            } catch (IOException ioe) {
+                throw new IllegalStateException("Mockito could not self-attach a jvmti agent to " +
+                        "the current VM. This feature is required for inline mocking.\nThis error" +
+                        " occured due to an I/O error during the creation of this agent: " + ioe
+                        + "\n\nPotentially, 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 StaticMockMethodAdvice 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, StaticMockMethodAdvice.class);
+            } catch (InvocationTargetException e) {
+                throw e.getCause();
+            }
+        } catch (Throwable throwable) {
+            agent = null;
+            initializationError = throwable;
+        }
+
+        AGENT = agent;
+        INITIALIZATION_ERROR = initializationError;
+    }
+
+    /**
+     * All currently active mock markers. We modify the class's byte code. Some objects of the class
+     * are modified, some are not. This list helps the {@link MockMethodAdvice} help figure out if a
+     * object's method calls should be intercepted.
+     */
+    private final HashMap<Object, InvocationHandlerAdapter> markerToHandler = new HashMap<>();
+    private final Map<Class, Object> classToMarker = new HashMap<>();
+
+    /**
+     * Class doing the actual byte code transformation.
+     */
+    private final StaticClassTransformer classTransformer;
+
+    /**
+     * Create a new mock maker.
+     */
+    public InlineStaticMockMaker() {
+        if (INITIALIZATION_ERROR != null) {
+            throw new RuntimeException("Could not initialize inline mock maker.\n" + "\n" +
+                    "Release: Android " + Build.VERSION.RELEASE + " " + Build.VERSION.INCREMENTAL
+                    + "Device: " + Build.BRAND + " " + Build.MODEL, INITIALIZATION_ERROR);
+        }
+
+        classTransformer = new StaticClassTransformer(AGENT, InlineDexmakerMockMaker
+                .DISPATCHER_CLASS, markerToHandler, classToMarker);
+    }
+
+    @Override
+    public <T> T createMock(MockCreationSettings<T> settings, MockHandler handler) {
+        Class<T> typeToMock = settings.getTypeToMock();
+        if (!typeToMock.equals(mockingInProgressClass.get()) || Modifier.isAbstract(typeToMock
+                .getModifiers())) {
+            return null;
+        }
+
+        Set<Class<?>> interfacesSet = settings.getExtraInterfaces();
+        InvocationHandlerAdapter handlerAdapter = new InvocationHandlerAdapter(handler);
+
+        classTransformer.mockClass(MockFeatures.withMockFeatures(typeToMock, interfacesSet));
+
+        Instantiator instantiator = Mockito.framework().getPlugins().getDefaultPlugin
+                (InstantiatorProvider2.class).getInstantiator(settings);
+
+        T mock;
+        try {
+            mock = instantiator.newInstance(typeToMock);
+        } catch (org.mockito.creation.instance.InstantiationException e) {
+            throw new MockitoException("Unable to create mock instance of type '" + typeToMock
+                    .getSimpleName() + "'", e);
+        }
+
+        if (classToMarker.containsKey(typeToMock)) {
+            throw new MockitoException(typeToMock + " is already mocked");
+        }
+        classToMarker.put(typeToMock, mock);
+
+        markerToHandler.put(mock, handlerAdapter);
+        return mock;
+    }
+
+    @Override
+    public void resetMock(Object mock, MockHandler newHandler, MockCreationSettings settings) {
+        InvocationHandlerAdapter adapter = getInvocationHandlerAdapter(mock);
+        if (adapter != null) {
+            if (mockingInProgressClass.get() == mock.getClass()) {
+                markerToHandler.remove(mock);
+                classToMarker.remove(mock.getClass());
+            } else {
+                adapter.setHandler(newHandler);
+            }
+        }
+    }
+
+    @Override
+    public TypeMockability isTypeMockable(final Class<?> type) {
+        if (mockingInProgressClass.get() == type) {
+            return new TypeMockability() {
+                @Override
+                public boolean mockable() {
+                    return !Modifier.isAbstract(type.getModifiers()) && !type.isPrimitive() && type
+                            != String.class;
+                }
+
+                @Override
+                public String nonMockableReason() {
+                    if (Modifier.isAbstract(type.getModifiers())) {
+                        return "abstract type";
+                    }
+
+                    if (type.isPrimitive()) {
+                        return "primitive type";
+                    }
+
+                    if (type == String.class) {
+                        return "string";
+                    }
+
+                    return "not handled type";
+                }
+            };
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public MockHandler getHandler(Object mock) {
+        InvocationHandlerAdapter adapter = getInvocationHandlerAdapter(mock);
+        return adapter != null ? adapter.getHandler() : null;
+    }
+
+    /**
+     * Get the {@link InvocationHandlerAdapter} registered for a marker.
+     *
+     * @param marker marker of the class that might have mocking set up
+     * @return adapter for this class, or {@code null} if not mocked
+     */
+    private InvocationHandlerAdapter getInvocationHandlerAdapter(Object marker) {
+        if (marker == null) {
+            return null;
+        }
+
+        return markerToHandler.get(marker);
+    }
+}
diff --git a/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/StaticClassTransformer.java b/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/StaticClassTransformer.java
new file mode 100644
index 0000000..a5ce9c0
--- /dev/null
+++ b/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/StaticClassTransformer.java
@@ -0,0 +1,190 @@
+/*
+ * 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.inline;
+
+import org.mockito.exceptions.base.MockitoException;
+
+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.Set;
+
+/**
+ * Adds entry hooks (that eventually call into
+ * {@link StaticMockMethodAdvice#handle(Object, Method, Object[])} to all static methods of the
+ * supplied classes.
+ * <p></p>Transforming a class to add entry hooks follow the following simple steps:
+ * <ol>
+ * <li>{@link #mockClass(MockFeatures)}</li>
+ * <li>{@link StaticJvmtiAgent#requestTransformClasses(Class[])}</li>
+ * <li>{@link StaticJvmtiAgent#nativeRetransformClasses(Class[])}</li>
+ * <li>agent.cc::Transform</li>
+ * <li>{@link StaticJvmtiAgent#runTransformers(ClassLoader, String, Class, ProtectionDomain,
+ *      byte[])}</li>
+ * <li>{@link #transform(Class, byte[])}</li>
+ * <li>{@link #nativeRedefine(String, byte[])}</li>
+ * </ol>
+ */
+class StaticClassTransformer {
+    // Some classes are so deeply optimized inside the runtime that they cannot be transformed
+    private static final Set<Class<? extends java.io.Serializable>> EXCLUDES = new HashSet<>(
+            Arrays.asList(Class.class,
+                    Boolean.class,
+                    Byte.class,
+                    Short.class,
+                    Character.class,
+                    Integer.class,
+                    Long.class,
+                    Float.class,
+                    Double.class,
+                    String.class));
+    /**
+     * We can only have a single transformation going on at a time, hence synchronize the
+     * transformation process via this lock.
+     *
+     * @see #mockClass(MockFeatures)
+     */
+    private final static Object lock = new Object();
+
+    /**
+     * Jvmti agent responsible for triggering transformations
+     */
+    private final StaticJvmtiAgent agent;
+
+    /**
+     * Types that have already be transformed
+     */
+    private final Set<Class<?>> mockedTypes;
+
+    /**
+     * A unique identifier that is baked into the transformed classes. The entry hooks will then
+     * pass this identifier to
+     * {@code com.android.dx.mockito.inline.MockMethodDispatcher#get(String, Object)} to
+     * find the advice responsible for handling the method call interceptions.
+     */
+    private final String identifier;
+
+    /**
+     * Create a new transformer.
+     */
+    StaticClassTransformer(StaticJvmtiAgent agent, Class dispatcherClass,
+                           Map<Object, InvocationHandlerAdapter> markerToHandler, Map<Class, Object>
+                                   classToMarker) {
+        this.agent = agent;
+        mockedTypes = Collections.synchronizedSet(new HashSet<Class<?>>());
+        identifier = String.valueOf(System.identityHashCode(this));
+        StaticMockMethodAdvice advice = new StaticMockMethodAdvice(markerToHandler, classToMarker);
+
+        try {
+            dispatcherClass.getMethod("set", String.class, Object.class).invoke(null, identifier,
+                    advice);
+        } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
+            throw new IllegalStateException(e);
+        }
+
+        agent.addTransformer(this);
+    }
+
+    /**
+     * Trigger the process to add entry hooks to a class (and all its parents).
+     *
+     * @param features specification what to mock
+     */
+    <T> void mockClass(MockFeatures<T> features) {
+        boolean subclassingRequired = !features.interfaces.isEmpty()
+                || Modifier.isAbstract(features.mockedType.getModifiers());
+
+        if (subclassingRequired
+                && !features.mockedType.isArray()
+                && !features.mockedType.isPrimitive()
+                && Modifier.isFinal(features.mockedType.getModifiers())) {
+            throw new MockitoException("Unsupported settings with this type '"
+                    + features.mockedType.getName() + "'");
+        }
+
+        synchronized (lock) {
+            Set<Class<?>> types = new HashSet<>();
+            Class<?> type = features.mockedType;
+
+            do {
+                boolean wasAdded = mockedTypes.add(type);
+
+                if (wasAdded) {
+                    if (!EXCLUDES.contains(type)) {
+                        types.add(type);
+                    }
+
+                    type = type.getSuperclass();
+                } else {
+                    break;
+                }
+            } while (type != null && !type.isInterface());
+
+            if (!types.isEmpty()) {
+                try {
+                    agent.requestTransformClasses(types.toArray(new Class<?>[types.size()]));
+                } catch (UnmodifiableClassException exception) {
+                    for (Class<?> failed : types) {
+                        mockedTypes.remove(failed);
+                    }
+
+                    throw new MockitoException("Could not modify all classes " + types, exception);
+                }
+            }
+        }
+    }
+
+    /**
+     * Add entry hooks to all methods of a class.
+     * <p>Called by the agent after triggering the transformation via
+     * {@link #mockClass(MockFeatures)}.
+     *
+     * @param classBeingRedefined class the hooks should be added to
+     * @param classfileBuffer     original byte code of the class
+     * @return transformed class
+     */
+    byte[] transform(Class<?> classBeingRedefined, byte[] classfileBuffer) throws
+            IllegalClassFormatException {
+        if (classBeingRedefined == null
+                || !mockedTypes.contains(classBeingRedefined)) {
+            return null;
+        } else {
+            try {
+                return nativeRedefine(identifier, classfileBuffer);
+            } catch (Throwable throwable) {
+                throw new IllegalClassFormatException();
+            }
+        }
+    }
+
+    /**
+     * 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-extended/src/main/java/com/android/dx/mockito/inline/StaticJvmtiAgent.java b/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/StaticJvmtiAgent.java
new file mode 100644
index 0000000..63a4e49
--- /dev/null
+++ b/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/StaticJvmtiAgent.java
@@ -0,0 +1,128 @@
+/*
+ * 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.inline;
+
+import android.os.Build;
+import android.os.Debug;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.security.ProtectionDomain;
+import java.util.ArrayList;
+
+import dalvik.system.BaseDexClassLoader;
+
+/**
+ * Interface to the native jvmti agent in agent.cc
+ */
+class StaticJvmtiAgent {
+    private static final String AGENT_LIB_NAME = "libstaticjvmtiagent.so";
+
+    private static final Object lock = new Object();
+
+    /**
+     * Registered byte code transformers
+     */
+    private final ArrayList<StaticClassTransformer> transformers = new ArrayList<>();
+
+    /**
+     * Enable jvmti and load agent.
+     * <p><b>If there are more than agent transforming classes the other agent might remove
+     * transformations added by this agent.</b>
+     *
+     * @throws IOException If jvmti could not be enabled or agent could not be loaded
+     */
+    StaticJvmtiAgent() throws IOException {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
+            throw new IOException("Requires API level " + Build.VERSION_CODES.P + ". API level is "
+                    + Build.VERSION.SDK_INT);
+        }
+
+        ClassLoader cl = StaticJvmtiAgent.class.getClassLoader();
+        if (!(cl instanceof BaseDexClassLoader)) {
+            throw new IOException("Could not load jvmti plugin as StaticJvmtiAgent class was not loaded "
+                    + "by a BaseDexClassLoader");
+        }
+
+        Debug.attachJvmtiAgent(AGENT_LIB_NAME, null, cl);
+        nativeRegisterTransformerHook();
+    }
+
+    private native void nativeRegisterTransformerHook();
+
+    private native void nativeUnregisterTransformerHook();
+
+    @Override
+    protected void finalize() throws Throwable {
+        nativeUnregisterTransformerHook();
+    }
+
+    /**
+     * Ask the agent to trigger transformation of some classes. This will extract the byte code of
+     * the classes and the call back the {@link #addTransformer(StaticClassTransformer)
+     * transformers} for each individual class.
+     *
+     * @param classes The classes to transform
+     * @throws UnmodifiableClassException If one of the classes can not be transformed
+     */
+    void requestTransformClasses(Class<?>[] classes) throws UnmodifiableClassException {
+        synchronized (lock) {
+            try {
+                nativeRetransformClasses(classes);
+            } catch (RuntimeException e) {
+                throw new UnmodifiableClassException(e);
+            }
+        }
+    }
+
+    // called by JNI
+    @SuppressWarnings("unused")
+    public boolean shouldTransform(Class<?> classBeingRedefined) {
+        for (StaticClassTransformer 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[])}.
+     *
+     * @param transformer the transformer to add.
+     */
+    void addTransformer(StaticClassTransformer transformer) {
+        transformers.add(transformer);
+    }
+
+    // called by JNI
+    @SuppressWarnings("unused")
+    public byte[] runTransformers(ClassLoader loader, String className,
+                                  Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
+                                  byte[] classfileBuffer) throws IllegalClassFormatException {
+        byte[] transformedByteCode = classfileBuffer;
+        for (StaticClassTransformer transformer : transformers) {
+            transformedByteCode = transformer.transform(classBeingRedefined, transformedByteCode);
+        }
+
+        return transformedByteCode;
+    }
+
+    private native void nativeRetransformClasses(Class<?>[] classes);
+}
diff --git a/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/StaticMockMethodAdvice.java b/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/StaticMockMethodAdvice.java
new file mode 100644
index 0000000..7a6f273
--- /dev/null
+++ b/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/StaticMockMethodAdvice.java
@@ -0,0 +1,377 @@
+/*
+ * Copyright (c) 2018 Mockito contributors
+ * This program is made available under the terms of the MIT License.
+ */
+
+package com.android.dx.mockito.inline;
+
+import java.lang.ref.WeakReference;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.function.BiConsumer;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static com.android.dx.mockito.inline.InlineStaticMockMaker.onMethodCallDuringStubbing;
+import static com.android.dx.mockito.inline.InlineStaticMockMaker.onMethodCallDuringVerification;
+
+/**
+ * Backend for the method entry hooks. Checks if the hooks should cause an interception or should
+ * be ignored.
+ */
+class StaticMockMethodAdvice {
+    /**
+     * Pattern to decompose a instrumentedMethodWithTypeAndSignature
+     */
+    private final static Pattern methodPattern = Pattern.compile("(.*)#(.*)\\((.*)\\)");
+    private final Map<Object, InvocationHandlerAdapter> markersToHandler;
+    private final Map<Class, Object> classToMarker;
+    @SuppressWarnings("ThreadLocalUsage")
+    private final SelfCallInfo selfCallInfo = new SelfCallInfo();
+
+    StaticMockMethodAdvice(Map<Object, InvocationHandlerAdapter> markerToHandler, Map<Class, Object>
+            classToMarker) {
+        this.markersToHandler = markerToHandler;
+        this.classToMarker = classToMarker;
+    }
+
+    /**
+     * Try to invoke the method {@code origin}.
+     *
+     * @param origin    method to invoke
+     * @param arguments arguments to the method
+     * @return result of the method
+     * @throws Throwable Exception if thrown by the method
+     */
+    private static Object tryInvoke(Method origin, Object[] arguments)
+            throws Throwable {
+        try {
+            return origin.invoke(null, arguments);
+        } catch (InvocationTargetException exception) {
+            throw exception.getCause();
+        }
+    }
+
+    private static Class<?> classForTypeName(String name) throws ClassNotFoundException {
+        if (name.endsWith("[]")) {
+            return Class.forName("[L" + name.substring(0, name.length() - 2) + ";");
+        } else {
+            return Class.forName(name);
+        }
+    }
+
+    private static Class nameToType(String name) throws ClassNotFoundException {
+        switch (name) {
+            case "byte":
+                return Byte.TYPE;
+            case "short":
+                return Short.TYPE;
+            case "int":
+                return Integer.TYPE;
+            case "long":
+                return Long.TYPE;
+            case "char":
+                return Character.TYPE;
+            case "float":
+                return Float.TYPE;
+            case "double":
+                return Double.TYPE;
+            case "boolean":
+                return Boolean.TYPE;
+            case "byte[]":
+                return byte[].class;
+            case "short[]":
+                return short[].class;
+            case "int[]":
+                return int[].class;
+            case "long[]":
+                return long[].class;
+            case "char[]":
+                return char[].class;
+            case "float[]":
+                return float[].class;
+            case "double[]":
+                return double[].class;
+            case "boolean[]":
+                return boolean[].class;
+            default:
+                return classForTypeName(name);
+        }
+    }
+
+    /**
+     * Would a call to SubClass.method handled by SuperClass.method ?
+     * <p>This is the case when subclass or any intermediate parent does not override method.
+     *
+     * @param subclass         Class that might have been called
+     * @param superClass       Class defining the method
+     * @param methodName       Name of method
+     * @param methodParameters Parameter of method
+     * @return {code true} iff the method would have be handled by superClass
+     */
+    private static boolean isMethodDefinedBySuperClass(Class<?> subclass, Class<?> superClass,
+                                                       String methodName,
+                                                       Class<?>[] methodParameters) {
+        do {
+            if (subclass == superClass) {
+                // The method is not overridden in the subclass or any class in between subClass
+                // and superClass.
+                return true;
+            }
+
+            try {
+                subclass.getDeclaredMethod(methodName, methodParameters);
+
+                // method is overridden is sub-class. hence the call could not have handled by
+                // the super-class.
+                return false;
+            } catch (NoSuchMethodException e) {
+                subclass = subclass.getSuperclass();
+            }
+        } while (subclass != null);
+
+        // Subclass is not a sub class of superClass
+        return false;
+    }
+
+    private static List<Class<?>> getAllSubclasses(Class<?> superClass, Collection<Class>
+            possibleSubClasses) {
+        ArrayList<Class<?>> subclasses = new ArrayList<>();
+        for (Class<?> possibleSubClass : possibleSubClasses) {
+            if (superClass.isAssignableFrom(possibleSubClass)) {
+                subclasses.add(possibleSubClass);
+            }
+        }
+
+        return subclasses;
+    }
+
+    private synchronized static native String nativeGetCalledClassName(Thread currentThread);
+
+    private Class<?> getClassMethodWasCalledOn(MethodDesc methodDesc) throws ClassNotFoundException,
+            NoSuchMethodException {
+        Class<?> classDeclaringMethod = classForTypeName(methodDesc.className);
+
+        /* If a sub-class does not override a static method, the super-classes method is called
+         * directly. Hence 'classDeclaringMethod' will be the super class. As the mocking of
+         * this and the class actually called might be different we need to find the class that
+         * was actually called.
+         */
+        if (Modifier.isFinal(classDeclaringMethod.getModifiers())
+                || Modifier.isFinal(classDeclaringMethod.getDeclaredMethod(methodDesc.methodName,
+                methodDesc.methodParamTypes).getModifiers())) {
+            return classDeclaringMethod;
+        } else {
+            boolean mightBeMocked = false;
+            // if neither the defining class nor any subclass of it is mocked, no point of
+            // trying to figure out the called class as isMocked will soon be checked.
+            for (Class<?> subClass : getAllSubclasses(classDeclaringMethod, classToMarker.keySet())) {
+                if (isMethodDefinedBySuperClass(subClass, classDeclaringMethod,
+                        methodDesc.methodName, methodDesc.methodParamTypes)) {
+                    mightBeMocked = true;
+                    break;
+                }
+            }
+
+            if (!mightBeMocked) {
+                return null;
+            }
+
+            String calledClassName = nativeGetCalledClassName(Thread.currentThread());
+            return Class.forName(calledClassName);
+        }
+    }
+
+    /**
+     * Get the method specified by {@code methodWithTypeAndSignature}.
+     *
+     * @param ignored
+     * @param methodWithTypeAndSignature the description of the method
+     * @return method {@code methodWithTypeAndSignature} refer to
+     */
+    @SuppressWarnings("unused")
+    public Method getOrigin(Object ignored, String methodWithTypeAndSignature) throws Throwable {
+        MethodDesc methodDesc = new MethodDesc(methodWithTypeAndSignature);
+
+        Class clazz = getClassMethodWasCalledOn(methodDesc);
+        if (clazz == null) {
+            return null;
+        }
+
+        Object marker = classToMarker.get(clazz);
+        if (!isMocked(marker)) {
+            return null;
+        }
+
+        return Class.forName(methodDesc.className).getDeclaredMethod(methodDesc.methodName,
+                methodDesc.methodParamTypes);
+    }
+
+    /**
+     * Handle a method entry hook.
+     *
+     * @param origin    method that contains the hook
+     * @param arguments arguments to the method
+     * @return A callable that can be called to get the mocked result or null if the method is not
+     * mocked.
+     */
+    @SuppressWarnings("unused")
+    public Callable<?> handle(Object methodDescStr, Method origin, Object[] arguments) throws
+            Throwable {
+        MethodDesc methodDesc = new MethodDesc((String) methodDescStr);
+        Class clazz = getClassMethodWasCalledOn(methodDesc);
+
+        Object marker = classToMarker.get(clazz);
+        InvocationHandlerAdapter interceptor = markersToHandler.get(marker);
+        if (interceptor == null) {
+            return null;
+        }
+
+        // extended.StaticCapableStubber#whenInt
+        BiConsumer<Class<?>, Method> onStub = onMethodCallDuringStubbing.get();
+        if (onStub != null) {
+            onStub.accept(clazz, origin);
+        }
+
+        // extended.ExtendedMockito#verifyInt
+        BiConsumer<Class<?>, Method> onVerify = onMethodCallDuringVerification.get();
+        if (onVerify != null) {
+            onVerify.accept(clazz, origin);
+        }
+
+        return new ReturnValueWrapper(interceptor.interceptEntryHook(marker, origin, arguments,
+                new SuperMethodCall(selfCallInfo, origin, marker, arguments)));
+    }
+
+    /**
+     * Checks if an {@code marker} is a mock marker.
+     *
+     * @return {@code true} iff the marker is a mock marker
+     */
+    public boolean isMarker(Object marker) {
+        return markersToHandler.containsKey(marker);
+    }
+
+    /**
+     * Check if this method call should be mocked. Usually the same as {@link #isMarker(Object)} but
+     * takes into account the state of {@link #selfCallInfo} that allows to temporary disable
+     * mocking for a single method call.
+     */
+    public boolean isMocked(Object marker) {
+        return selfCallInfo.shouldMockMethod(marker) && isMarker(marker);
+    }
+
+    private static class MethodDesc {
+        final String className;
+        final String methodName;
+        final Class<?>[] methodParamTypes;
+
+        private MethodDesc(String methodWithTypeAndSignature) throws ClassNotFoundException {
+            Matcher methodComponents = methodPattern.matcher(methodWithTypeAndSignature);
+            boolean wasFound = methodComponents.find();
+            if (!wasFound) {
+                throw new IllegalArgumentException();
+            }
+
+            className = methodComponents.group(1);
+            methodName = methodComponents.group(2);
+            String methodParamTypeNames[] = methodComponents.group(3).split(",");
+
+            ArrayList<Class<?>> methodParamTypesList = new ArrayList<>(methodParamTypeNames.length);
+            for (String methodParamName : methodParamTypeNames) {
+                if (!methodParamName.equals("")) {
+                    methodParamTypesList.add(nameToType(methodParamName));
+                }
+            }
+            methodParamTypes = methodParamTypesList.toArray(new Class<?>[]{});
+        }
+
+        @Override
+        public String toString() {
+            return className + "#" + methodName;
+        }
+    }
+
+    /**
+     * Used to call the real (non mocked) method.
+     */
+    private static class SuperMethodCall implements InvocationHandlerAdapter.SuperMethod {
+        private final SelfCallInfo selfCallInfo;
+        private final Method origin;
+        private final WeakReference<Object> marker;
+        private final Object[] arguments;
+
+        private SuperMethodCall(SelfCallInfo selfCallInfo, Method origin, Object marker,
+                                Object[] arguments) {
+            this.selfCallInfo = selfCallInfo;
+            this.origin = origin;
+            this.marker = new WeakReference(marker);
+            this.arguments = arguments;
+        }
+
+        /**
+         * Call the read (non mocked) method.
+         *
+         * @return Result of read method
+         * @throws Throwable thrown by the read method
+         */
+        @Override
+        public Object invoke() throws Throwable {
+            if (!Modifier.isPublic(origin.getDeclaringClass().getModifiers()
+                    & origin.getModifiers())) {
+                origin.setAccessible(true);
+            }
+
+            // By setting instance in the the selfCallInfo, once single method call on this instance
+            // and thread will call the read method as isMocked will return false.
+            selfCallInfo.set(marker.get());
+            return tryInvoke(origin, arguments);
+        }
+
+    }
+
+    /**
+     * Stores a return value of {@link #handle(Object, Method, Object[])} and returns in on
+     * {@link #call()}.
+     */
+    private static class ReturnValueWrapper implements Callable<Object> {
+        private final Object returned;
+
+        private ReturnValueWrapper(Object returned) {
+            this.returned = returned;
+        }
+
+        @Override
+        public Object call() {
+            return returned;
+        }
+    }
+
+    /**
+     * Used to call the original method. If a instance is {@link #set(Object)}
+     * {@link #shouldMockMethod(Object)} returns false for this instance once.
+     * <p>This is {@link ThreadLocal}, so a thread can {@link #set(Object)} and instance and then
+     * call {@link #shouldMockMethod(Object)} without interference.
+     *
+     * @see SuperMethodCall#invoke()
+     * @see #isMocked(Object)
+     */
+    private static class SelfCallInfo extends ThreadLocal<Object> {
+        boolean shouldMockMethod(Object value) {
+            Object current = get();
+
+            if (current == value) {
+                set(null);
+                return false;
+            } else {
+                return true;
+            }
+        }
+    }
+}
diff --git a/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/extended/ExtendedMockito.java b/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/extended/ExtendedMockito.java
new file mode 100644
index 0000000..19da75b
--- /dev/null
+++ b/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/extended/ExtendedMockito.java
@@ -0,0 +1,444 @@
+/*
+ * 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.inline.extended;
+
+import org.mockito.InOrder;
+import org.mockito.MockSettings;
+import org.mockito.Mockito;
+import org.mockito.internal.matchers.LocalizedMatcher;
+import org.mockito.internal.progress.ArgumentMatcherStorageImpl;
+import org.mockito.stubbing.Answer;
+import org.mockito.verification.VerificationMode;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.android.dx.mockito.inline.InlineDexmakerMockMaker.onSpyInProgressInstance;
+import static com.android.dx.mockito.inline.InlineStaticMockMaker.onMethodCallDuringVerification;
+import static org.mockito.internal.progress.ThreadSafeMockingProgress.mockingProgress;
+
+/**
+ * Mockito extended with the ability to stub static methods.
+ * <p>E.g.
+ * <pre>
+ *     private class C {
+ *         static int staticMethod(String arg) {
+ *             return 23;
+ *         }
+ *     }
+ *
+ *    {@literal @}Test
+ *     public void test() {
+ *         // static mocking
+ *         MockitoSession session = mockitoSession().staticSpy(C.class).startMocking();
+ *         try {
+ *             doReturn(42).when(() -> {return C.staticMethod(eq("Arg"));});
+ *             assertEquals(42, C.staticMethod("Arg"));
+ *             verify(() -> C.staticMethod(eq("Arg"));
+ *         } finally {
+ *             session.finishMocking();
+ *         }
+ *     }
+ * </pre>
+ * <p>It is possible to use this class for instance mocking too. Hence you can use it as a full
+ * replacement for {@link Mockito}.
+ * <p>This is a prototype that is intended to eventually be upstreamed into mockito proper. Some
+ * APIs might change. All such APIs are annotated with {@link UnstableApi}.
+ */
+@UnstableApi
+public class ExtendedMockito extends Mockito {
+    /**
+     * Currently active {@link #mockitoSession() sessions}
+     */
+    private static ArrayList<StaticMockitoSession> sessions = new ArrayList<>();
+
+    /**
+     * Same as {@link Mockito#doAnswer(Answer)} but adds the ability to stub static method calls via
+     * {@link StaticCapableStubber#when(MockedMethod)} and
+     * {@link StaticCapableStubber#when(MockedVoidMethod)}.
+     */
+    public static StaticCapableStubber doAnswer(Answer answer) {
+        return new StaticCapableStubber(Mockito.doAnswer(answer));
+    }
+
+    /**
+     * Same as {@link Mockito#doCallRealMethod()} but adds the ability to stub static method calls
+     * via {@link StaticCapableStubber#when(MockedMethod)} and
+     * {@link StaticCapableStubber#when(MockedVoidMethod)}.
+     */
+    public static StaticCapableStubber doCallRealMethod() {
+        return new StaticCapableStubber(Mockito.doCallRealMethod());
+    }
+
+    /**
+     * Same as {@link Mockito#doNothing()} but adds the ability to stub static method calls via
+     * {@link StaticCapableStubber#when(MockedMethod)} and
+     * {@link StaticCapableStubber#when(MockedVoidMethod)}.
+     */
+    public static StaticCapableStubber doNothing() {
+        return new StaticCapableStubber(Mockito.doNothing());
+    }
+
+    /**
+     * Same as {@link Mockito#doReturn(Object)} but adds the ability to stub static method calls
+     * via {@link StaticCapableStubber#when(MockedMethod)} and
+     * {@link StaticCapableStubber#when(MockedVoidMethod)}.
+     */
+    public static StaticCapableStubber doReturn(Object toBeReturned) {
+        return new StaticCapableStubber(Mockito.doReturn(toBeReturned));
+    }
+
+    /**
+     * Same as {@link Mockito#doReturn(Object, Object...)} but adds the ability to stub static
+     * method calls via {@link StaticCapableStubber#when(MockedMethod)} and
+     * {@link StaticCapableStubber#when(MockedVoidMethod)}.
+     */
+    public static StaticCapableStubber doReturn(Object toBeReturned, Object... toBeReturnedNext) {
+        return new StaticCapableStubber(Mockito.doReturn(toBeReturned, toBeReturnedNext));
+    }
+
+    /**
+     * Same as {@link Mockito#doThrow(Class)} but adds the ability to stub static method calls via
+     * {@link StaticCapableStubber#when(MockedMethod)} and
+     * {@link StaticCapableStubber#when(MockedVoidMethod)}.
+     */
+    public static StaticCapableStubber doThrow(Class<? extends Throwable> toBeThrown) {
+        return new StaticCapableStubber(Mockito.doThrow(toBeThrown));
+    }
+
+    /**
+     * Same as {@link Mockito#doThrow(Class, Class...)} but adds the ability to stub static method
+     * calls via {@link StaticCapableStubber#when(MockedMethod)} and
+     * {@link StaticCapableStubber#when(MockedVoidMethod)}.
+     */
+    @SafeVarargs
+    public static StaticCapableStubber doThrow(Class<? extends Throwable> toBeThrown,
+                                               Class<? extends Throwable>... toBeThrownNext) {
+        return new StaticCapableStubber(Mockito.doThrow(toBeThrown, toBeThrownNext));
+    }
+
+    /**
+     * Same as {@link Mockito#doThrow(Throwable...)} but adds the ability to stub static method
+     * calls via {@link StaticCapableStubber#when(MockedMethod)} and
+     * {@link StaticCapableStubber#when(MockedVoidMethod)}.
+     */
+    public static StaticCapableStubber doThrow(Throwable... toBeThrown) {
+        return new StaticCapableStubber(Mockito.doThrow(toBeThrown));
+    }
+
+    /**
+     * Many methods of mockito take mock objects. To be able to call the same methods for static
+     * mocking, this method gets a marker object that can be used instead.
+     *
+     * @param clazz The class object the marker should be crated for
+     * @return A marker object. This should not be used directly. It can only be passed into other
+     * ExtendedMockito methods.
+     * @see #inOrder(Object...)
+     * @see #clearInvocations(Object...)
+     * @see #ignoreStubs(Object...)
+     * @see #mockingDetails(Object)
+     * @see #reset(Object[])
+     * @see #verifyNoMoreInteractions(Object...)
+     * @see #verifyZeroInteractions(Object...)
+     */
+    @UnstableApi
+    @SuppressWarnings("unchecked")
+    public static <T> T staticMockMarker(Class<T> clazz) {
+        for (StaticMockitoSession session : sessions) {
+            T marker = session.staticMockMarker(clazz);
+
+            if (marker != null) {
+                return marker;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Same as {@link #staticMockMarker(Class)} but for multiple classes at once.
+     */
+    @UnstableApi
+    public static Object[] staticMockMarker(Class<?>... clazz) {
+        Object[] markers = new Object[clazz.length];
+
+        for (int i = 0; i < clazz.length; i++) {
+            for (StaticMockitoSession session : sessions) {
+                markers[i] = session.staticMockMarker(clazz[i]);
+
+                if (markers[i] != null) {
+                    break;
+                }
+            }
+
+            if (markers[i] == null) {
+                return null;
+            }
+        }
+
+        return markers;
+    }
+
+    /**
+     * Make an existing object a spy.
+     *
+     * <p>This does <u>not</u> clone the existing objects. If a method is stubbed on a spy
+     * converted by this method all references to the already existing object will be affected by
+     * the stubbing.
+     *
+     * @param toSpy The existing object to convert into a spy
+     */
+    @UnstableApi
+    @SuppressWarnings("CheckReturnValue")
+    public static void spyOn(Object toSpy) {
+        if (onSpyInProgressInstance.get() != null) {
+            throw new IllegalStateException("Cannot set up spying on an existing object while "
+                    + "setting up spying for another existing object");
+        }
+
+        onSpyInProgressInstance.set(toSpy);
+        try {
+            spy(toSpy);
+        } finally {
+            onSpyInProgressInstance.remove();
+        }
+    }
+
+    /**
+     * To be used for static mocks/spies in place of {@link Mockito#verify(Object)} when calling
+     * void methods.
+     * <p>E.g.
+     * <pre>
+     *     private class C {
+     *         void instanceMethod(String arg) {}
+     *         static void staticMethod(String arg) {}
+     *     }
+     *
+     *    {@literal @}Test
+     *     public void test() {
+     *         // instance mocking
+     *         C mock = mock(C.class);
+     *         mock.instanceMethod("Hello");
+     *         verify(mock).mockedVoidInstanceMethod(eq("Hello"));
+     *
+     *         // static mocking
+     *         MockitoSession session = mockitoSession().staticMock(C.class).startMocking();
+     *         C.staticMethod("World");
+     *         verify(() -> C.staticMethod(eq("World"));
+     *         session.finishMocking();
+     *     }
+     * </pre>
+     */
+    public static void verify(MockedVoidMethod method) {
+        verify(method, times(1));
+    }
+
+    /**
+     * To be used for static mocks/spies in place of {@link Mockito#verify(Object)}.
+     * <p>E.g. (please notice the 'return' in the lambda when verifying the static call)
+     * <pre>
+     *     private class C {
+     *         int instanceMethod(String arg) {
+     *             return 1;
+     *         }
+     *
+     *         int static staticMethod(String arg) {
+     *             return 2;
+     *         }
+     *     }
+     *
+     *    {@literal @}Test
+     *     public void test() {
+     *         // instance mocking
+     *         C mock = mock(C.class);
+     *         mock.instanceMethod("Hello");
+     *         verify(mock).mockedVoidInstanceMethod(eq("Hello"));
+     *
+     *         // static mocking
+     *         MockitoSession session = mockitoSession().staticMock(C.class).startMocking();
+     *         C.staticMethod("World");
+     *         verify(() -> <b>{return</b> C.staticMethod(eq("World")<b>;}</b>);
+     *         session.finishMocking();
+     *     }
+     * </pre>
+     */
+    @UnstableApi
+    public static void verify(MockedMethod method) {
+        verify(method, times(1));
+    }
+
+    /**
+     * To be used for static mocks/spies in place of
+     * {@link Mockito#verify(Object, VerificationMode)} when calling void methods.
+     *
+     * @see #verify(MockedVoidMethod)
+     */
+    @UnstableApi
+    public static void verify(MockedVoidMethod method, VerificationMode mode) {
+        verifyInt(method, mode, null);
+    }
+
+    /**
+     * To be used for static mocks/spies in place of
+     * {@link Mockito#verify(Object, VerificationMode)}.
+     *
+     * @see #verify(MockedMethod)
+     */
+    @UnstableApi
+    public static void verify(MockedMethod method, VerificationMode mode) {
+        verify((MockedVoidMethod) method::get, mode);
+    }
+
+    /**
+     * Same as {@link Mockito#inOrder(Object...)} but adds the ability to verify static method
+     * calls via {@link StaticInOrder#verify(MockedMethod)},
+     * {@link StaticInOrder#verify(MockedVoidMethod)},
+     * {@link StaticInOrder#verify(MockedMethod, VerificationMode)}, and
+     * {@link StaticInOrder#verify(MockedVoidMethod, VerificationMode)}.
+     * <p>To verify static method calls, the result of {@link #staticMockMarker(Class)} has to be
+     * passed to the {@code mocksAndMarkers} parameter. It is possible to mix static and instance
+     * mocking.
+     */
+    @UnstableApi
+    public static StaticInOrder inOrder(Object... mocksAndMarkers) {
+        return new StaticInOrder(Mockito.inOrder(mocksAndMarkers));
+    }
+
+    /**
+     * Same as {@link Mockito#mockitoSession()} but adds the ability to mock static methods
+     * calls via {@link StaticMockitoSessionBuilder#mockStatic(Class)},
+     * {@link StaticMockitoSessionBuilder#mockStatic(Class, Answer)}, and {@link
+     * StaticMockitoSessionBuilder#mockStatic(Class, MockSettings)};
+     * <p>All mocking spying will be removed once the session is finished.
+     */
+    public static StaticMockitoSessionBuilder mockitoSession() {
+        return new StaticMockitoSessionBuilder(Mockito.mockitoSession());
+    }
+
+    /**
+     * Common implementation of verification of static method calls.
+     *
+     * @param method          The static method call to be verified
+     * @param mode            The verification mode
+     * @param instanceInOrder If set, the {@link StaticInOrder} object
+     */
+    @SuppressWarnings({"CheckReturnValue", "MockitoUsage", "unchecked"})
+    static void verifyInt(MockedVoidMethod method, VerificationMode mode, InOrder
+            instanceInOrder) {
+        if (onMethodCallDuringVerification.get() != null) {
+            throw new IllegalStateException("Verification is already in progress on this "
+                    + "thread.");
+        }
+
+        ArrayList<Method> verifications = new ArrayList<>();
+
+        /* Set up callback that is triggered when the next static method is called on this thread.
+         *
+         * This is necessary as we don't know which class the method will be called on. Once the
+         * call is intercepted this will
+         *    1. Remove all matchers (e.g. eq(), any()) from the matcher stack
+         *    2. Call verify on the marker for the class
+         *    3. Add the markers back to the stack
+         */
+        onMethodCallDuringVerification.set((clazz, verifiedMethod) -> {
+            // TODO: O holy reflection! Let's hope we can integrate this better.
+            try {
+                ArgumentMatcherStorageImpl argMatcherStorage = (ArgumentMatcherStorageImpl)
+                        mockingProgress().getArgumentMatcherStorage();
+                List<LocalizedMatcher> matchers;
+
+                // Matcher are called before verify, hence remove the from the storage
+                Method resetStackMethod
+                        = argMatcherStorage.getClass().getDeclaredMethod("resetStack");
+                resetStackMethod.setAccessible(true);
+
+                matchers = (List<LocalizedMatcher>) resetStackMethod.invoke(argMatcherStorage);
+
+                if (instanceInOrder == null) {
+                    verify(staticMockMarker(clazz), mode);
+                } else {
+                    instanceInOrder.verify(staticMockMarker(clazz), mode);
+                }
+
+                // Add the matchers back after verify is called
+                Field matcherStackField
+                        = argMatcherStorage.getClass().getDeclaredField("matcherStack");
+                matcherStackField.setAccessible(true);
+
+                Method pushMethod = matcherStackField.getType().getDeclaredMethod("push",
+                        Object.class);
+
+                for (LocalizedMatcher matcher : matchers) {
+                    pushMethod.invoke(matcherStackField.get(argMatcherStorage), matcher);
+                }
+            } catch (NoSuchFieldException | NoSuchMethodException | IllegalAccessException
+                    | InvocationTargetException | ClassCastException e) {
+                throw new Error("Reflection failed. Do you use a compatible version of "
+                        + "mockito?", e);
+            }
+
+            verifications.add(verifiedMethod);
+        });
+        try {
+            try {
+                // Trigger the method call. This call will be intercepted and trigger the
+                // onMethodCallDuringVerification callback.
+                method.run();
+            } catch (Throwable t) {
+                if (t instanceof RuntimeException) {
+                    throw (RuntimeException) t;
+                } else if (t instanceof Error) {
+                    throw (Error) t;
+                }
+                throw new RuntimeException(t);
+            }
+
+            if (verifications.isEmpty()) {
+                // Make sure something was intercepted
+                throw new IllegalArgumentException("Nothing was verified. Does the lambda call "
+                        + "a static method on a 'static' mock/spy ?");
+            } else if (verifications.size() > 1) {
+                // A lambda might call several methods. In this case it is not clear what should
+                // be verified. Hence throw an error.
+                throw new IllegalArgumentException("Multiple intercepted calls on methods "
+                        + verifications);
+            }
+        } finally {
+            onMethodCallDuringVerification.remove();
+        }
+    }
+
+    /**
+     * Register a new session.
+     *
+     * @param session Session to register
+     */
+    static void addSession(StaticMockitoSession session) {
+        sessions.add(session);
+    }
+
+    /**
+     * Remove a finished session.
+     *
+     * @param session Session to remove
+     */
+    static void removeSession(StaticMockitoSession session) {
+        sessions.remove(session);
+    }
+}
diff --git a/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/extended/MockedMethod.java b/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/extended/MockedMethod.java
new file mode 100644
index 0000000..accc65a
--- /dev/null
+++ b/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/extended/MockedMethod.java
@@ -0,0 +1,27 @@
+/*
+ * 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.inline.extended;
+
+/**
+ * A call to a method that should be stubbed / verified.
+ *
+ * @param <T> return type of the method
+ */
+@UnstableApi
+public interface MockedMethod<T> {
+    T get() throws Throwable;
+}
diff --git a/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/extended/MockedVoidMethod.java b/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/extended/MockedVoidMethod.java
new file mode 100644
index 0000000..e84b58d
--- /dev/null
+++ b/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/extended/MockedVoidMethod.java
@@ -0,0 +1,25 @@
+/*
+ * 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.inline.extended;
+
+/**
+ * A call to a void method that should be stubbed / verified.
+ */
+@UnstableApi
+public interface MockedVoidMethod {
+    void run() throws Throwable;
+}
diff --git a/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/extended/StaticCapableStubber.java b/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/extended/StaticCapableStubber.java
new file mode 100644
index 0000000..0872f82
--- /dev/null
+++ b/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/extended/StaticCapableStubber.java
@@ -0,0 +1,204 @@
+/*
+ * 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.inline.extended;
+
+import org.mockito.stubbing.Answer;
+import org.mockito.stubbing.Stubber;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+
+import static com.android.dx.mockito.inline.InlineStaticMockMaker.onMethodCallDuringStubbing;
+
+/**
+ * Same as {@link Stubber} but supports settings up stubbing of static methods via
+ * {@link #when(MockedMethod)} and {@link #when(MockedVoidMethod)}.
+ */
+@UnstableApi
+public class StaticCapableStubber implements Stubber {
+    private Stubber instanceStubber;
+
+    StaticCapableStubber(Stubber instanceStubber) {
+        this.instanceStubber = instanceStubber;
+    }
+
+    @Override
+    public <T> T when(T mock) {
+        return instanceStubber.when(mock);
+    }
+
+    /**
+     * Common implementation of all {@code doReturn.when} calls.
+     *
+     * @param method The static method to be stubbed
+     */
+    private void whenInt(MockedVoidMethod method) {
+        if (onMethodCallDuringStubbing.get() != null) {
+            throw new IllegalStateException("Stubbing is already in progress on this thread.");
+        }
+
+        ArrayList<Method> stubbingsSetUp = new ArrayList<>();
+
+        /* Set up interception of method. 'method' does not specify what class the stubbing is
+         * set up on. Hence wait until the call is made and intercept it just before the code
+         * is executed. At this time, start the stubbing operation.
+         */
+        onMethodCallDuringStubbing.set((clazz, stubbedMethod) -> {
+            when(ExtendedMockito.staticMockMarker(clazz));
+            stubbingsSetUp.add(stubbedMethod);
+        });
+        try {
+            try {
+                // Call the method. This will be intercepted by onMethodCallDuringStubbing
+                method.run();
+            } catch (Throwable t) {
+                throw new RuntimeException(t);
+            }
+
+            if (stubbingsSetUp.isEmpty()) {
+                // Make sure something was intercepted
+                throw new IllegalArgumentException("Nothing was stubbed. Does the lambda call a"
+                        + " static method on a 'static' mock/spy?");
+            } else if (stubbingsSetUp.size() > 1) {
+                // A lambda might call several methods. In this case it is not clear what should
+                // be stubbed. Hence throw an error.
+                throw new IllegalArgumentException("Multiple intercepted calls on method "
+                        + stubbingsSetUp);
+            }
+        } finally {
+            onMethodCallDuringStubbing.remove();
+        }
+    }
+
+    /**
+     * Set up stubbing for a static void method.
+     * <pre>
+     *     private class C {
+     *         void instanceMethod(String arg) {}
+     *         static void staticMethod(String arg) {}
+     *     }
+     *
+     *    {@literal @}Test
+     *     public void test() {
+     *         // instance mocking
+     *         C mock = mock(C.class);
+     *         doThrow(Exception.class).when(mock).instanceMethod(eq("Hello));
+     *         assertThrows(Exception.class, mock.instanceMethod("Hello"));
+     *
+     *         // static mocking
+     *         MockitoSession session = mockitoSession().staticMock(C.class).startMocking();
+     *         doThrow(Exception.class).when(() -> C.instanceMethod(eq("Hello));
+     *         assertThrows(Exception.class, C.staticMethod("Hello"));
+     *         session.finishMocking();
+     *     }
+     * </pre>
+     *
+     * @param method The method to stub as a lambda. This should only call a single stubbable
+     *               static method.
+     */
+    @UnstableApi
+    public void when(MockedVoidMethod method) {
+        whenInt(method);
+    }
+
+    /**
+     * Set up stubbing for a static method.
+     * <pre>
+     *     private class C {
+     *         int instanceMethod(String arg) {
+     *             return 1;
+     *         }
+     *
+     *         int static staticMethod(String arg) {
+     *             return 1;
+     *         }
+     *     }
+     *
+     *    {@literal @}Test
+     *     public void test() {
+     *         // instance mocking
+     *         C mock = mock(C.class);
+     *         doReturn(2).when(mock).instanceMethod(eq("Hello));
+     *         assertEquals(2, mock.instanceMethod("Hello"));
+     *
+     *         // static mocking
+     *         MockitoSession session = mockitoSession().staticMock(C.class).startMocking();
+     *         doReturn(2).when(() -> C.instanceMethod(eq("Hello));
+     *         assertEquals(2, C.staticMethod("Hello"));
+     *         session.finishMocking();
+     *     }
+     * </pre>
+     *
+     * @param method The method to stub as a lambda. This should only call a single stubbable
+     *               static method.
+     * @param <T>    Return type of the stubbed method
+     */
+    @UnstableApi
+    public <T> void when(MockedMethod<T> method) {
+        whenInt(method::get);
+    }
+
+    @Override
+    public StaticCapableStubber doThrow(Throwable... toBeThrown) {
+        instanceStubber = instanceStubber.doThrow(toBeThrown);
+        return this;
+    }
+
+    @Override
+    public StaticCapableStubber doThrow(Class<? extends Throwable> toBeThrown) {
+        instanceStubber = instanceStubber.doThrow(toBeThrown);
+        return this;
+    }
+
+    @SafeVarargs
+    @Override
+    public final StaticCapableStubber doThrow(Class<? extends Throwable> toBeThrown,
+                                              Class<? extends Throwable>... nextToBeThrown) {
+        instanceStubber = instanceStubber.doThrow(toBeThrown, nextToBeThrown);
+        return this;
+    }
+
+    @Override
+    public StaticCapableStubber doAnswer(Answer answer) {
+        instanceStubber = instanceStubber.doAnswer(answer);
+        return this;
+    }
+
+    @Override
+    public StaticCapableStubber doNothing() {
+        instanceStubber = instanceStubber.doNothing();
+        return this;
+    }
+
+    @Override
+    public StaticCapableStubber doReturn(Object toBeReturned) {
+        instanceStubber = instanceStubber.doReturn(toBeReturned);
+        return this;
+    }
+
+    @Override
+    public StaticCapableStubber doReturn(Object toBeReturned, Object... nextToBeReturned) {
+        instanceStubber = instanceStubber.doReturn(toBeReturned, nextToBeReturned);
+        return this;
+    }
+
+    @Override
+    public StaticCapableStubber doCallRealMethod() {
+        instanceStubber = instanceStubber.doCallRealMethod();
+        return this;
+    }
+}
diff --git a/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/extended/StaticInOrder.java b/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/extended/StaticInOrder.java
new file mode 100644
index 0000000..312a454
--- /dev/null
+++ b/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/extended/StaticInOrder.java
@@ -0,0 +1,148 @@
+/*
+ * 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.inline.extended;
+
+import org.mockito.InOrder;
+import org.mockito.Mockito;
+import org.mockito.verification.VerificationMode;
+
+/**
+ * Same as {@link InOrder} but adds the ability to verify static method calls via
+ * {@link #verify(MockedMethod)}, {@link #verify(MockedVoidMethod)},
+ * {@link #verify(MockedMethod, VerificationMode)}, and
+ * {@link #verify(MockedVoidMethod, VerificationMode)}.
+ */
+@UnstableApi
+public class StaticInOrder implements InOrder {
+    private final InOrder instanceInOrder;
+
+    StaticInOrder(InOrder inOrder) {
+        instanceInOrder = inOrder;
+    }
+
+    @Override
+    public <T> T verify(T mock) {
+        return instanceInOrder.verify(mock);
+    }
+
+    @Override
+    public <T> T verify(T mock, VerificationMode mode) {
+        return instanceInOrder.verify(mock, mode);
+    }
+
+    /**
+     * To be used for static mocks/spies in place of {@link #verify(Object)} when calling void
+     * methods.
+     * <p>E.g.
+     * <pre>
+     *     private class C {
+     *         void instanceMethod(String arg) {}
+     *         void void staticMethod(String arg) {}
+     *     }
+     *
+     *    {@literal @}Test
+     *     public void test() {
+     *         // instance mocking
+     *         C mock = mock(C.class);
+     *         mock.staticMethod("Hello");
+     *         mock.instanceMethod("World");
+     *         inOrder().verify(mock).mockedVoidInstanceMethod(eq("Hello"));
+     *         inOrder().verify(mock).mockedVoidInstanceMethod(eq("World"));
+     *
+     *         // static mocking
+     *         MockitoSession session = mockitoSession().staticMock(C.class).startMocking();
+     *         C.staticMethod("Hello");
+     *         C.staticMethod("World");
+     *
+     *         StaticInOrder inOrder = inOrder();
+     *         inOrder.verify(() -> C.staticMethod(eq("Hello"));
+     *         inOrder.verify(() -> C.staticMethod(eq("World"));
+     *         session.finishMocking();
+     *     }
+     * </pre>
+     */
+    public void verify(MockedVoidMethod method) {
+        verify(method, Mockito.times(1));
+    }
+
+    /**
+     * To be used for static mocks/spies in place of {@link #verify(Object)}.
+     * <p>E.g.
+     * <pre>
+     *     private class C {
+     *         int instanceMethod(String arg) {
+     *             return 1;
+     *         }
+     *
+     *         int static staticMethod(String arg) {
+     *             return 2;
+     *         }
+     *     }
+     *
+     *    {@literal @}Test
+     *     public void test() {
+     *         // instance mocking
+     *         C mock = mock(C.class);
+     *         mock.instanceMethod("Hello");
+     *         mock.instanceMethod("World");
+     *         inOrder().verify(mock).mockedVoidInstanceMethod(eq("Hello"));
+     *         inOrder().verify(mock).mockedVoidInstanceMethod(eq("World"));
+     *
+     *         // static mocking
+     *         MockitoSession session = mockitoSession().staticMock(C.class).startMocking();
+     *         C.staticMethod("Hello");
+     *         C.staticMethod("World");
+     *
+     *         StaticInOrder inOrder = inOrder();
+     *         inOrder.verify(() -> C.staticMethod(eq("Hello"));
+     *         inOrder.verify(() -> C.staticMethod(eq("World"));
+     *         session.finishMocking();
+     *     }
+     * </pre>
+     */
+    @UnstableApi
+    public void verify(MockedMethod method) {
+        verify(method, Mockito.times(1));
+    }
+
+    /**
+     * To be used for static mocks/spies in place of
+     * {@link InOrder#verify(Object, VerificationMode)} when calling void methods.
+     *
+     * @see #verify(MockedVoidMethod)
+     */
+    @UnstableApi
+    public void verify(MockedVoidMethod method, VerificationMode mode) {
+        ExtendedMockito.verifyInt(method, mode, instanceInOrder);
+    }
+
+    /**
+     * To be used for static mocks/spies in place of
+     * {@link InOrder#verify(Object, VerificationMode)}.
+     *
+     * @see #verify(MockedMethod)
+     */
+    @UnstableApi
+    public void verify(MockedMethod method, VerificationMode mode) {
+        verify((MockedVoidMethod) method::get, mode);
+    }
+
+    @Override
+    public void verifyNoMoreInteractions() {
+        instanceInOrder.verifyNoMoreInteractions();
+    }
+}
diff --git a/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/extended/StaticMocking.java b/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/extended/StaticMocking.java
new file mode 100644
index 0000000..071e4af
--- /dev/null
+++ b/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/extended/StaticMocking.java
@@ -0,0 +1,34 @@
+/*
+ * 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.inline.extended;
+
+import java.util.function.Supplier;
+
+/**
+ * Data class for a class and the way to create the marker object for the class. As all
+ * invocations are routed to the marker the way we create the marker also determines the other
+ * properties of the mock.
+ */
+class StaticMocking<T> {
+    final Class<T> clazz;
+    final Supplier<T> markerSupplier;
+
+    StaticMocking(Class<T> clazz, Supplier<T> markerSupplier) {
+        this.clazz = clazz;
+        this.markerSupplier = markerSupplier;
+    }
+}
diff --git a/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/extended/StaticMockitoSession.java b/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/extended/StaticMockitoSession.java
new file mode 100644
index 0000000..851d4a4
--- /dev/null
+++ b/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/extended/StaticMockitoSession.java
@@ -0,0 +1,117 @@
+/*
+ * 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.inline.extended;
+
+import org.mockito.Mockito;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import static com.android.dx.mockito.inline.InlineStaticMockMaker.mockingInProgressClass;
+
+/**
+ * Same as {@link MockitoSession} but used when static methods are also stubbed.
+ */
+@UnstableApi
+public class StaticMockitoSession implements MockitoSession {
+    /**
+     * For each class where static mocking is enabled there is one marker object.
+     */
+    private static final HashMap<Class, Object> classToMarker = new HashMap<>();
+
+    private final MockitoSession instanceSession;
+    private final ArrayList<Class<?>> staticMocks = new ArrayList<>(0);
+
+    StaticMockitoSession(MockitoSession instanceSession) {
+        ExtendedMockito.addSession(this);
+        this.instanceSession = instanceSession;
+    }
+
+    @Override
+    public void setStrictness(Strictness strictness) {
+        instanceSession.setStrictness(strictness);
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p><b>Extension:</b> This also resets all stubbing of static methods set up in the
+     * {@link ExtendedMockito#mockitoSession() builder} of the session.
+     */
+    @Override
+    public void finishMocking() {
+        finishMocking(null);
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p><b>Extension:</b> This also resets all stubbing of static methods set up in the
+     * {@link ExtendedMockito#mockitoSession() builder} of the session.
+     */
+    @Override
+    public void finishMocking(Throwable failure) {
+        try {
+            instanceSession.finishMocking(failure);
+        } finally {
+            for (Class<?> clazz : staticMocks) {
+                mockingInProgressClass.set(clazz);
+                try {
+                    Mockito.reset(ExtendedMockito.staticMockMarker(clazz));
+                } finally {
+                    mockingInProgressClass.remove();
+                }
+                classToMarker.remove(clazz);
+            }
+
+            ExtendedMockito.removeSession(this);
+        }
+    }
+
+    /**
+     * Init mocking for a class.
+     *
+     * @param mocking Description and settings of the mocking
+     * @param <T>     The class to mock
+     */
+    <T> void mockStatic(StaticMocking<T> mocking) {
+        if (ExtendedMockito.staticMockMarker(mocking.clazz) != null) {
+            throw new IllegalArgumentException(mocking.clazz + " is already mocked");
+        }
+
+        mockingInProgressClass.set(mocking.clazz);
+        try {
+            classToMarker.put(mocking.clazz, mocking.markerSupplier.get());
+        } finally {
+            mockingInProgressClass.remove();
+        }
+
+        staticMocks.add(mocking.clazz);
+    }
+
+    /**
+     * Get marker for a mocked/spies class or {@code null}.
+     *
+     * @param clazz The class that is mocked
+     * @return marker for a mocked class or {@code null} if class is not mocked in this session
+     * @see ExtendedMockito#staticMockMarker(Class)
+     */
+    @SuppressWarnings("unchecked")
+    <T> T staticMockMarker(Class<T> clazz) {
+        return (T) classToMarker.get(clazz);
+    }
+}
diff --git a/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/extended/StaticMockitoSessionBuilder.java b/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/extended/StaticMockitoSessionBuilder.java
new file mode 100644
index 0000000..c40fc89
--- /dev/null
+++ b/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/extended/StaticMockitoSessionBuilder.java
@@ -0,0 +1,157 @@
+/*
+ * 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.inline.extended;
+
+import org.mockito.MockSettings;
+import org.mockito.Mockito;
+import org.mockito.exceptions.misusing.UnfinishedMockingSessionException;
+import org.mockito.quality.Strictness;
+import org.mockito.session.MockitoSessionBuilder;
+import org.mockito.session.MockitoSessionLogger;
+import org.mockito.stubbing.Answer;
+
+import java.util.ArrayList;
+
+import static org.mockito.Mockito.CALLS_REAL_METHODS;
+import static org.mockito.Mockito.withSettings;
+
+/**
+ * Same as {@link MockitoSessionBuilder} but adds the ability to stub static methods
+ * calls via {@link #mockStatic(Class)}, {@link #mockStatic(Class, Answer)}, and
+ * {@link #mockStatic(Class, MockSettings)};
+ * <p>All mocks/spies will be reset once the session is finished.
+ */
+@UnstableApi
+public class StaticMockitoSessionBuilder implements MockitoSessionBuilder {
+    private final ArrayList<StaticMocking> staticMockings = new ArrayList<>(0);
+    private MockitoSessionBuilder instanceSessionBuilder;
+
+    StaticMockitoSessionBuilder(MockitoSessionBuilder instanceSessionBuilder) {
+        this.instanceSessionBuilder = instanceSessionBuilder;
+    }
+
+    /**
+     * Sets up mocking for all static methods of a class. All methods will return the default value.
+     * <p>This changes the behavior of <u>all</u> static methods calls for <u>all</u>
+     * invocations. In most cases using {@link #spyStatic(Class)} and stubbing only a few
+     * methods can be used.
+     *
+     * @param clazz The class to set up static mocking for
+     * @return This builder
+     */
+    @UnstableApi
+    public <T> StaticMockitoSessionBuilder mockStatic(Class<T> clazz) {
+        staticMockings.add(new StaticMocking<>(clazz, () -> Mockito.mock(clazz)));
+        return this;
+    }
+
+    /**
+     * Sets up mocking for sall tatic methods of a class. All methods will call the {@code
+     * defaultAnswer}.
+     * <p>This changes the behavior of <u>all</u> static methods calls for <u>all</u>
+     * invocations. In most cases using {@link #spyStatic(Class)} and stubbing only a few
+     * methods can be used.
+     *
+     * @param clazz         The class to set up static mocking for
+     * @param defaultAnswer The answer to return by default
+     * @return This builder
+     */
+    @UnstableApi
+    public <T> StaticMockitoSessionBuilder mockStatic(Class<T> clazz, Answer defaultAnswer) {
+        staticMockings.add(new StaticMocking<>(clazz, () -> Mockito.mock(clazz, defaultAnswer)));
+        return this;
+    }
+
+    /**
+     * Sets up mocking for all static methods of a class with custom {@link MockSettings}.
+     * <p>This changes the behavior of <u>all</u> static methods calls for <u>all</u>
+     * invocations. In most cases using {@link #spyStatic(Class)} and stubbing only a few
+     * methods can be used.
+     *
+     * @param clazz    The class to set up static mocking for
+     * @param settings Settings used to set up the mock.
+     * @return This builder
+     */
+    @UnstableApi
+    public <T> StaticMockitoSessionBuilder mockStatic(Class<T> clazz, MockSettings settings) {
+        staticMockings.add(new StaticMocking<>(clazz, () -> Mockito.mock(clazz, settings)));
+        return this;
+    }
+
+    /**
+     * Sets up spying for static methods of a class.
+     *
+     * @param clazz The class to set up static spying for
+     * @return This builder
+     */
+    @UnstableApi
+    public <T> StaticMockitoSessionBuilder spyStatic(Class<T> clazz) {
+        staticMockings.add(new StaticMocking<>(clazz, () -> Mockito.mock(clazz, withSettings()
+                .defaultAnswer(CALLS_REAL_METHODS))));
+        return this;
+    }
+
+    @Override
+    public StaticMockitoSessionBuilder initMocks(Object testClassInstance) {
+        instanceSessionBuilder = instanceSessionBuilder.initMocks(testClassInstance);
+        return this;
+    }
+
+    @Override
+    public StaticMockitoSessionBuilder initMocks(Object... testClassInstances) {
+        instanceSessionBuilder = instanceSessionBuilder.initMocks(testClassInstances);
+        return this;
+    }
+
+    @Override
+    public StaticMockitoSessionBuilder name(String name) {
+        instanceSessionBuilder = instanceSessionBuilder.name(name);
+        return this;
+    }
+
+    @Override
+    public StaticMockitoSessionBuilder strictness(Strictness strictness) {
+        instanceSessionBuilder = instanceSessionBuilder.strictness(strictness);
+        return this;
+    }
+
+    @Override
+    public StaticMockitoSessionBuilder logger(MockitoSessionLogger logger) {
+        instanceSessionBuilder = instanceSessionBuilder.logger(logger);
+        return this;
+    }
+
+    @Override
+    public StaticMockitoSession startMocking() throws UnfinishedMockingSessionException {
+        StaticMockitoSession session
+                = new StaticMockitoSession(instanceSessionBuilder.startMocking());
+        try {
+            for (StaticMocking mocking : staticMockings) {
+                session.mockStatic((StaticMocking<?>) mocking);
+            }
+        } catch (Throwable t) {
+            try {
+                session.finishMocking();
+            } catch (Throwable ignored) {
+                // suppress all failures
+            }
+            throw t;
+        }
+
+        return session;
+    }
+}
diff --git a/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/extended/UnstableApi.java b/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/extended/UnstableApi.java
new file mode 100644
index 0000000..379b2fa
--- /dev/null
+++ b/dexmaker-mockito-inline-extended/src/main/java/com/android/dx/mockito/inline/extended/UnstableApi.java
@@ -0,0 +1,31 @@
+/*
+ * 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.inline.extended;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * The API is not a mockito API and there is a chance in might change in future versions. Some
+ * classes inherit from Mockito classes. In this case the class name is not stable, but the
+ * inherited methods are.
+ */
+@Retention(RetentionPolicy.CLASS)
+@Documented
+public @interface UnstableApi {
+}
diff --git a/dexmaker-mockito-inline-extended/src/main/jni/staticjvmtiagent/agent.cc b/dexmaker-mockito-inline-extended/src/main/jni/staticjvmtiagent/agent.cc
new file mode 100644
index 0000000..5fcc4b1
--- /dev/null
+++ b/dexmaker-mockito-inline-extended/src/main/jni/staticjvmtiagent/agent.cc
@@ -0,0 +1,835 @@
+/*
+ * 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.
+ */
+
+#include <cstdlib>
+#include <sstream>
+#include <cstring>
+#include <cassert>
+#include <cstdarg>
+#include <algorithm>
+
+#include <jni.h>
+
+#include "jvmti.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;
+
+namespace com_android_dx_mockito_inline {
+    static jvmtiEnv* localJvmtiEnv;
+
+    static jobject sTransformer;
+
+    // Converts a class name to a type descriptor
+    // (ex. "java.lang.String" to "Ljava/lang/String;")
+    static std::string
+    ClassNameToDescriptor(const char* class_name) {
+        std::stringstream ss;
+        ss << "L";
+        for (auto p = class_name; *p != '\0'; ++p) {
+            ss << (*p == '.' ? '/' : *p);
+        }
+        ss << ";";
+        return ss.str();
+    }
+
+    // Takes the full dex file for class 'classBeingRedefined'
+    // - isolates the dex code for the class out of the dex file
+    // - calls sTransformer.runTransformers on the isolated dex code
+    // - send the transformed code back to the runtime
+    static void
+    Transform(jvmtiEnv* jvmti_env,
+              JNIEnv* env,
+              jclass classBeingRedefined,
+              jobject loader,
+              const char* name,
+              jobject protectionDomain,
+              jint classDataLen,
+              const unsigned char* classData,
+              jint* newClassDataLen,
+              unsigned char** newClassData) {
+        if (sTransformer != nullptr) {
+            // 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);
+
+            u4 index = reader.FindClassIndex(ClassNameToDescriptor(name).c_str());
+            reader.CreateClassIr(index);
+            std::shared_ptr<ir::DexFile> ir = reader.GetIr();
+
+            struct Allocator : public Writer::Allocator {
+                virtual void* Allocate(size_t size) {return ::malloc(size);}
+                virtual void Free(void* ptr) {::free(ptr);}
+            };
+
+            Allocator allocator;
+            Writer writer(ir);
+            size_t isolatedClassLen = 0;
+            std::shared_ptr<jbyte> isolatedClass((jbyte*)writer.CreateImage(&allocator,
+                                                                            &isolatedClassLen));
+
+            // Create jbyteArray with isolated byte code of class
+            jbyteArray isolatedClassArr = env->NewByteArray(isolatedClassLen);
+            env->SetByteArrayRegion(isolatedClassArr, 0, isolatedClassLen,
+                                    isolatedClass.get());
+
+            jstring nameStr = env->NewStringUTF(name);
+
+            // Call JvmtiAgent#runTransformers
+            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,
+                                                                           runTransformersMethod,
+                                                                           loader, nameStr,
+                                                                           classBeingRedefined,
+                                                                           protectionDomain,
+                                                                           isolatedClassArr);
+
+            // Set transformed byte code
+            if (!env->ExceptionOccurred() && transformedArr != nullptr) {
+                *newClassDataLen = env->GetArrayLength(transformedArr);
+
+                jbyte* transformed = env->GetByteArrayElements(transformedArr, 0);
+
+                jvmti_env->Allocate(*newClassDataLen, newClassData);
+                std::memcpy(*newClassData, transformed, *newClassDataLen);
+
+                env->ReleaseByteArrayElements(transformedArr, transformed, 0);
+            }
+        }
+    }
+
+    // Add a label before instructionAfter
+    static void
+    addLabel(CodeIr& c,
+             lir::Instruction* instructionAfter,
+             Label* returnTrueLabel) {
+        c.instructions.InsertBefore(instructionAfter, returnTrueLabel);
+    }
+
+    // Add a byte code before instructionAfter
+    static void
+    addInstr(CodeIr& c,
+             lir::Instruction* instructionAfter,
+             Opcode opcode,
+             const std::list<Operand*>& operands) {
+        auto instruction = c.Alloc<Bytecode>();
+
+        instruction->opcode = opcode;
+
+        for (auto it = operands.begin(); it != operands.end(); it++) {
+            instruction->operands.push_back(*it);
+        }
+
+        c.instructions.InsertBefore(instructionAfter, instruction);
+    }
+
+    // Add a method call byte code before instructionAfter
+    static void
+    addCall(ir::Builder& b,
+            CodeIr& c,
+            lir::Instruction* instructionAfter,
+            Opcode opcode,
+            ir::Type* type,
+            const char* methodName,
+            ir::Type* returnType,
+            const std::vector<ir::Type*>& types,
+            const std::list<int>& regs) {
+        auto proto = b.GetProto(returnType, b.GetTypeList(types));
+        auto method = b.GetMethodDecl(b.GetAsciiString(methodName), proto, type);
+
+        VRegList* param_regs = c.Alloc<VRegList>();
+        for (auto it = regs.begin(); it != regs.end(); it++) {
+            param_regs->registers.push_back(*it);
+        }
+
+        addInstr(c, instructionAfter, opcode, {param_regs, c.Alloc<Method>(method,
+                                                                           method->orig_index)});
+    }
+
+    typedef struct {
+        ir::Type* boxedType;
+        ir::Type* scalarType;
+        std::string unboxMethod;
+    } BoxingInfo;
+
+    // Get boxing / unboxing info for a type
+    static BoxingInfo
+    getBoxingInfo(ir::Builder &b,
+                  char typeCode) {
+        BoxingInfo boxingInfo;
+
+        if (typeCode != 'L' && typeCode !=  '[') {
+            std::stringstream tmp;
+            tmp << typeCode;
+            boxingInfo.scalarType = b.GetType(tmp.str().c_str());
+        }
+
+        switch (typeCode) {
+            case 'B':
+                boxingInfo.boxedType = b.GetType("Ljava/lang/Byte;");
+                boxingInfo.unboxMethod = "byteValue";
+                break;
+            case 'S':
+                boxingInfo.boxedType = b.GetType("Ljava/lang/Short;");
+                boxingInfo.unboxMethod = "shortValue";
+                break;
+            case 'I':
+                boxingInfo.boxedType = b.GetType("Ljava/lang/Integer;");
+                boxingInfo.unboxMethod = "intValue";
+                break;
+            case 'C':
+                boxingInfo.boxedType = b.GetType("Ljava/lang/Character;");
+                boxingInfo.unboxMethod = "charValue";
+                break;
+            case 'F':
+                boxingInfo.boxedType = b.GetType("Ljava/lang/Float;");
+                boxingInfo.unboxMethod = "floatValue";
+                break;
+            case 'Z':
+                boxingInfo.boxedType = b.GetType("Ljava/lang/Boolean;");
+                boxingInfo.unboxMethod = "booleanValue";
+                break;
+            case 'J':
+                boxingInfo.boxedType = b.GetType("Ljava/lang/Long;");
+                boxingInfo.unboxMethod = "longValue";
+                break;
+            case 'D':
+                boxingInfo.boxedType = b.GetType("Ljava/lang/Double;");
+                boxingInfo.unboxMethod = "doubleValue";
+                break;
+            default:
+                // real object
+                break;
+        }
+
+        return boxingInfo;
+    }
+
+    static size_t
+    getNumParams(ir::EncodedMethod *method) {
+        if (method->decl->prototype->param_types == nullptr) {
+            return 0;
+        }
+
+        return method->decl->prototype->param_types->types.size();
+    }
+
+    static bool
+    canBeTransformed(ir::EncodedMethod *method) {
+        std::string type = method->decl->parent->Decl();
+        ir::String* methodName = method->decl->name;
+
+        return ((method->access_flags & kAccStatic) != 0)
+               && !(((method->access_flags & (kAccPrivate | kAccBridge | kAccNative)) != 0)
+                    || (Utf8Cmp(methodName->c_str(), "<clinit>") == 0)
+                    || (strncmp(type.c_str(), "java.", 5) == 0
+                        && (method->access_flags & (kAccPrivate | kAccPublic | kAccProtected))
+                           == 0));
+    }
+
+    // Transforms the classes to add the mockito hooks
+    // - equals and hashcode are handled in a special way
+    extern "C" JNIEXPORT jbyteArray JNICALL
+    Java_com_android_dx_mockito_inline_StaticClassTransformer_nativeRedefine(JNIEnv* env,
+                                                                           jobject generator,
+                                                                           jstring idStr,
+                                                                           jbyteArray originalArr) {
+        unsigned char* original = (unsigned char*)env->GetByteArrayElements(originalArr, 0);
+
+        Reader reader(original, env->GetArrayLength(originalArr));
+        reader.CreateClassIr(0);
+        std::shared_ptr<ir::DexFile> dex_ir = reader.GetIr();
+        ir::Builder b(dex_ir);
+
+        ir::Type* objectT = b.GetType("Ljava/lang/Object;");
+        ir::Type* objectArrayT = b.GetType("[Ljava/lang/Object;");
+        ir::Type* stringT = b.GetType("Ljava/lang/String;");
+        ir::Type* methodT = b.GetType("Ljava/lang/reflect/Method;");
+        ir::Type* callableT = b.GetType("Ljava/util/concurrent/Callable;");
+        ir::Type* dispatcherT = b.GetType("Lcom/android/dx/mockito/inline/MockMethodDispatcher;");
+
+        // Add id to dex file
+        const char* idNative = env->GetStringUTFChars(idStr, 0);
+        ir::String* id = b.GetAsciiString(idNative);
+        env->ReleaseStringUTFChars(idStr, idNative);
+
+        for (auto& method : dex_ir->encoded_methods) {
+            if (!canBeTransformed(method.get())) {
+                continue;
+            }
+            /*
+            static long method_original(int param1, long param2, String param3) {
+                foo();
+                return bar();
+            }
+
+            static long method_transformed(int param1, long param2, String param3) {
+                // MockMethodDispatcher dispatcher = MockMethodDispatcher.get(idStr, this);
+                const-string v0, "65463hg34t"
+                const v1, 0
+                invoke-static {v0, v1}, MockMethodDispatcher.get(String, Object):MockMethodDispatcher
+                move-result-object v0
+
+                // if (dispatcher == null) {
+                //    goto original_method;
+                // }
+                if-eqz v0, original_method
+
+                // Method origin = dispatcher.getOrigin(this, methodDesc);
+                const-string v1 "fully.qualified.ClassName#original_method(int, long, String)"
+                const v2, 0
+                invoke-virtual {v0, v2, v1}, MockMethodDispatcher.getOrigin(Object, String):Method
+                move-result-object v1
+
+                // if (origin == null) {
+                //     goto original_method;
+                // }
+                if-eqz v1, original_method
+
+                // Create an array with Objects of all parameters.
+
+                //     Object[] arguments = new Object[3]
+                const v3, 3
+                new-array v2, v3, Object[]
+
+                //     Integer param1Integer = Integer.valueOf(param1)
+                move-from16 v3, ARG1     # this is necessary as invoke-static cannot deal with high
+                                         # registers and ARG1 might be high
+                invoke-static {v3}, Integer.valueOf(int):Integer
+                move-result-object v3
+
+                //     arguments[0] = param1Integer
+                const v4, 0
+                aput-object v3, v2, v4
+
+                //     Long param2Long = Long.valueOf(param2)
+                move-widefrom16 v3:v4, ARG2.1:ARG2.2 # this is necessary as invoke-static cannot
+                                                     # deal with high registers and ARG2 might be
+                                                     # high
+                invoke-static {v3, v4}, Long.valueOf(long):Long
+                move-result-object v3
+
+                //     arguments[1] = param2Long
+                const v4, 1
+                aput-object v3, v2, v4
+
+                //     arguments[2] = param3
+                const v4, 2
+                move-objectfrom16 v3, ARG3     # this is necessary as aput-object cannot deal with
+                                               # high registers and ARG3 might be high
+                aput-object v3, v2, v4
+
+                // Callable<?> mocked = dispatcher.handle(methodDesc --as this parameter--,
+                //                                        origin, arguments);
+                const-string v3 "fully.qualified.ClassName#original_method(int, long, String)"
+                invoke-virtual {v0,v3,v1,v2}, MockMethodDispatcher.handle(Object, Method,
+                                                                          Object[]):Callable
+                move-result-object v0
+
+                //  if (mocked != null) {
+                if-eqz v0, original_method
+
+                //      Object ret = mocked.call();
+                invoke-interface {v0}, Callable.call():Object
+                move-result-object v0
+
+                //      Long retLong = (Long)ret
+                check-cast v0, Long
+
+                //      long retlong = retLong.longValue();
+                invoke-virtual {v0}, Long.longValue():long
+                move-result-wide v0:v1
+
+                //      return retlong;
+                return-wide v0:v1
+
+                //  }
+
+            original_method:
+                // Move all method arguments down so that they match what the original code expects.
+                // Let's assume three arguments, one int, one long, one String and the and used to
+                // use 4 registers
+                move16 v5, v6             # ARG1
+                move-wide16 v6:v7, v7:v8  # ARG2 (overlapping moves are allowed)
+                move-object16 v8, v9      # ARG3
+
+                // foo();
+                // return bar();
+                unmodified original byte code
+            }
+            */
+
+            CodeIr c(method.get(), dex_ir);
+
+            // Make sure there are at least 5 local registers to use
+            int originalNumRegisters = method->code->registers - method->code->ins_count;
+            int numAdditionalRegs = std::max(0, 5 - originalNumRegisters);
+            int firstArg = originalNumRegisters + numAdditionalRegs;
+
+            if (numAdditionalRegs > 0) {
+                c.ir_method->code->registers += numAdditionalRegs;
+            }
+
+            lir::Instruction* fi = *(c.instructions.begin());
+
+            // Add methodDesc to dex file
+            std::stringstream ss;
+            ss << method->decl->parent->Decl() << "#" << method->decl->name->c_str() << "(" ;
+            bool first = true;
+            if (method->decl->prototype->param_types != nullptr) {
+                for (const auto& type : method->decl->prototype->param_types->types) {
+                    if (first) {
+                        first = false;
+                    } else {
+                        ss << ",";
+                    }
+
+                    ss << type->Decl().c_str();
+                }
+            }
+            ss << ")";
+            std::string methodDescStr = ss.str();
+            ir::String* methodDesc = b.GetAsciiString(methodDescStr.c_str());
+
+            size_t numParams = getNumParams(method.get());
+
+            Label* originalMethodLabel = c.Alloc<Label>(0);
+            CodeLocation* originalMethod = c.Alloc<CodeLocation>(originalMethodLabel);
+            VReg* v0 = c.Alloc<VReg>(0);
+            VReg* v1 = c.Alloc<VReg>(1);
+            VReg* v2 = c.Alloc<VReg>(2);
+            VReg* v3 = c.Alloc<VReg>(3);
+            VReg* v4 = c.Alloc<VReg>(4);
+
+            addInstr(c, fi, OP_CONST_STRING, {v0, c.Alloc<String>(id, id->orig_index)});
+            addInstr(c, fi, OP_CONST, {v1, c.Alloc<Const32>(0)});
+            addCall(b, c, fi, OP_INVOKE_STATIC, dispatcherT, "get", dispatcherT, {stringT, objectT},
+                    {0, 1});
+            addInstr(c, fi, OP_MOVE_RESULT_OBJECT, {v0});
+            addInstr(c, fi, OP_IF_EQZ, {v0, originalMethod});
+            addInstr(c, fi, OP_CONST_STRING,
+                     {v1, c.Alloc<String>(methodDesc, methodDesc->orig_index)});
+            addInstr(c, fi, OP_CONST, {v2, c.Alloc<Const32>(0)});
+            addCall(b, c, fi, OP_INVOKE_VIRTUAL, dispatcherT, "getOrigin", methodT,
+                    {objectT, stringT}, {0, 2, 1});
+            addInstr(c, fi, OP_MOVE_RESULT_OBJECT, {v1});
+            addInstr(c, fi, OP_IF_EQZ, {v1, originalMethod});
+            addInstr(c, fi, OP_CONST, {v3, c.Alloc<Const32>(numParams)});
+            addInstr(c, fi, OP_NEW_ARRAY, {v2, v3, c.Alloc<Type>(objectArrayT,
+                                                                 objectArrayT->orig_index)});
+
+            if (numParams > 0) {
+                int argReg = firstArg;
+
+                for (int argNum = 0; argNum < numParams; argNum++) {
+                    const auto& type = method->decl->prototype->param_types->types[argNum];
+                    BoxingInfo boxingInfo = getBoxingInfo(b, type->descriptor->c_str()[0]);
+
+                    switch (type->GetCategory()) {
+                        case ir::Type::Category::Scalar:
+                            addInstr(c, fi, OP_MOVE_FROM16, {v3, c.Alloc<VReg>(argReg)});
+                            addCall(b, c, fi, OP_INVOKE_STATIC, boxingInfo.boxedType, "valueOf",
+                                    boxingInfo.boxedType, {type}, {3});
+                            addInstr(c, fi, OP_MOVE_RESULT_OBJECT, {v3});
+
+                            argReg++;
+                            break;
+                        case ir::Type::Category::WideScalar: {
+                            VRegPair* v3v4 = c.Alloc<VRegPair>(3);
+                            VRegPair* argRegPair = c.Alloc<VRegPair>(argReg);
+
+                            addInstr(c, fi, OP_MOVE_WIDE_FROM16, {v3v4, argRegPair});
+                            addCall(b, c, fi, OP_INVOKE_STATIC, boxingInfo.boxedType, "valueOf",
+                                    boxingInfo.boxedType, {type}, {3, 4});
+                            addInstr(c, fi, OP_MOVE_RESULT_OBJECT, {v3});
+
+                            argReg += 2;
+                            break;
+                        }
+                        case ir::Type::Category::Reference:
+                            addInstr(c, fi, OP_MOVE_OBJECT_FROM16, {v3, c.Alloc<VReg>(argReg)});
+
+                            argReg++;
+                            break;
+                        case ir::Type::Category::Void:
+                            assert(false);
+                    }
+
+                    addInstr(c, fi, OP_CONST, {v4, c.Alloc<Const32>(argNum)});
+                    addInstr(c, fi, OP_APUT_OBJECT, {v3, v2, v4});
+                }
+            }
+
+            // NASTY Hack: Push in method name as "mock"
+            addInstr(c, fi, OP_CONST_STRING,
+                     {v3, c.Alloc<String>(methodDesc, methodDesc->orig_index)});
+            addCall(b, c, fi, OP_INVOKE_VIRTUAL, dispatcherT, "handle", callableT,
+                    {objectT, methodT, objectArrayT}, {0, 3, 1, 2});
+            addInstr(c, fi, OP_MOVE_RESULT_OBJECT, {v0});
+            addInstr(c, fi, OP_IF_EQZ, {v0, originalMethod});
+            addCall(b, c, fi, OP_INVOKE_INTERFACE, callableT, "call", objectT, {}, {0});
+            addInstr(c, fi, OP_MOVE_RESULT_OBJECT, {v0});
+
+            ir::Type *returnType = method->decl->prototype->return_type;
+            BoxingInfo boxingInfo = getBoxingInfo(b, returnType->descriptor->c_str()[0]);
+
+            switch (returnType->GetCategory()) {
+                case ir::Type::Category::Scalar:
+                    addInstr(c, fi, OP_CHECK_CAST, {v0,
+                                                    c.Alloc<Type>(boxingInfo.boxedType, boxingInfo.boxedType->orig_index)});
+                    addCall(b, c, fi, OP_INVOKE_VIRTUAL, boxingInfo.boxedType,
+                            boxingInfo.unboxMethod.c_str(), returnType, {}, {0});
+                    addInstr(c, fi, OP_MOVE_RESULT, {v0});
+                    addInstr(c, fi, OP_RETURN, {v0});
+                    break;
+                case ir::Type::Category::WideScalar: {
+                    VRegPair* v0v1 = c.Alloc<VRegPair>(0);
+
+                    addInstr(c, fi, OP_CHECK_CAST, {v0,
+                                                    c.Alloc<Type>(boxingInfo.boxedType, boxingInfo.boxedType->orig_index)});
+                    addCall(b, c, fi, OP_INVOKE_VIRTUAL, boxingInfo.boxedType,
+                            boxingInfo.unboxMethod.c_str(), returnType, {}, {0});
+                    addInstr(c, fi, OP_MOVE_RESULT_WIDE, {v0v1});
+                    addInstr(c, fi, OP_RETURN_WIDE, {v0v1});
+                    break;
+                }
+                case ir::Type::Category::Reference:
+                    addInstr(c, fi, OP_CHECK_CAST, {v0, c.Alloc<Type>(returnType,
+                                                                      returnType->orig_index)});
+                    addInstr(c, fi, OP_RETURN_OBJECT, {v0});
+                    break;
+                case ir::Type::Category::Void:
+                    addInstr(c, fi, OP_RETURN_VOID, {});
+                    break;
+            }
+
+            addLabel(c, fi, originalMethodLabel);
+
+            if (numParams > 0) {
+                int argReg = firstArg;
+
+                for (int argNum = 0; argNum < numParams; argNum++) {
+                    const auto& type = method->decl->prototype->param_types->types[argNum];
+                    int origReg = argReg - numAdditionalRegs;
+                    switch (type->GetCategory()) {
+                        case ir::Type::Category::Scalar:
+                            addInstr(c, fi, OP_MOVE_16, {c.Alloc<VReg>(origReg),
+                                                         c.Alloc<VReg>(argReg)});
+                            argReg++;
+                            break;
+                        case ir::Type::Category::WideScalar:
+                            addInstr(c, fi, OP_MOVE_WIDE_16,{c.Alloc<VRegPair>(origReg),
+                                                             c.Alloc<VRegPair>(argReg)});
+                            argReg +=2;
+                            break;
+                        case ir::Type::Category::Reference:
+                            addInstr(c, fi, OP_MOVE_OBJECT_16, {c.Alloc<VReg>(origReg),
+                                                                c.Alloc<VReg>(argReg)});
+                            argReg++;
+                            break;
+                    }
+                }
+            }
+
+            c.Assemble();
+        }
+
+        struct Allocator : public Writer::Allocator {
+            virtual void* Allocate(size_t size) {return ::malloc(size);}
+            virtual void Free(void* ptr) {::free(ptr);}
+        };
+
+        Allocator allocator;
+        Writer writer(dex_ir);
+        size_t transformedLen = 0;
+        std::shared_ptr<jbyte> transformed((jbyte*)writer.CreateImage(&allocator, &transformedLen));
+
+        jbyteArray transformedArr = env->NewByteArray(transformedLen);
+        env->SetByteArrayRegion(transformedArr, 0, transformedLen, transformed.get());
+
+        return transformedArr;
+    }
+
+    // Initializes the agent
+    extern "C" jint Agent_OnAttach(JavaVM* vm,
+                                   char* options,
+                                   void* reserved) {
+        jint jvmError = vm->GetEnv(reinterpret_cast<void**>(&localJvmtiEnv), JVMTI_VERSION_1_2);
+        if (jvmError != JNI_OK) {
+            return jvmError;
+        }
+
+        jvmtiCapabilities caps;
+        memset(&caps, 0, sizeof(caps));
+        caps.can_retransform_classes = 1;
+
+        jvmtiError error = localJvmtiEnv->AddCapabilities(&caps);
+        if (error != JVMTI_ERROR_NONE) {
+            return error;
+        }
+
+        jvmtiEventCallbacks cb;
+        memset(&cb, 0, sizeof(cb));
+        cb.ClassFileLoadHook = Transform;
+
+        error = localJvmtiEnv->SetEventCallbacks(&cb, sizeof(cb));
+        if (error != JVMTI_ERROR_NONE) {
+            return error;
+        }
+
+        error = localJvmtiEnv->SetEventNotificationMode(JVMTI_ENABLE,
+                                                        JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, nullptr);
+        if (error != JVMTI_ERROR_NONE) {
+            return error;
+        }
+
+        return JVMTI_ERROR_NONE;
+    }
+
+    // Throw runtime exception
+    static void throwRuntimeExpection(JNIEnv* env, const char* fmt, ...) {
+        char msgBuf[512];
+
+        va_list args;
+        va_start (args, fmt);
+        vsnprintf(msgBuf, sizeof(msgBuf), fmt, args);
+        va_end (args);
+
+        jclass exceptionClass = env->FindClass("java/lang/RuntimeException");
+        env->ThrowNew(exceptionClass, msgBuf);
+    }
+
+    // Register transformer hook
+    extern "C" JNIEXPORT void JNICALL
+    Java_com_android_dx_mockito_inline_StaticJvmtiAgent_nativeRegisterTransformerHook(JNIEnv* env,
+                                                                                     jobject thiz) {
+        sTransformer = env->NewGlobalRef(thiz);
+    }
+
+    // Unregister transformer hook
+    extern "C" JNIEXPORT void JNICALL
+    Java_com_android_dx_mockito_inline_StaticJvmtiAgent_nativeUnregisterTransformerHook(JNIEnv* env,
+                                                                                     jobject thiz) {
+        env->DeleteGlobalRef(sTransformer);
+        sTransformer = nullptr;
+    }
+
+    // Triggers retransformation of classes via this file's Transform method
+    extern "C" JNIEXPORT void JNICALL
+    Java_com_android_dx_mockito_inline_StaticJvmtiAgent_nativeRetransformClasses(JNIEnv* env,
+                                                                             jobject thiz,
+                                                                             jobjectArray classes) {
+        jsize numTransformedClasses = env->GetArrayLength(classes);
+        jclass *transformedClasses = (jclass*) malloc(numTransformedClasses * sizeof(jclass));
+        for (int i = 0; i < numTransformedClasses; i++) {
+            transformedClasses[i] = (jclass) env->NewGlobalRef(env->GetObjectArrayElement(classes, i));
+        }
+
+        jvmtiError error = localJvmtiEnv->RetransformClasses(numTransformedClasses,
+                                                             transformedClasses);
+
+        for (int i = 0; i < numTransformedClasses; i++) {
+            env->DeleteGlobalRef(transformedClasses[i]);
+        }
+        free(transformedClasses);
+
+        if (error != JVMTI_ERROR_NONE) {
+            throwRuntimeExpection(env, "Could not retransform classes: %d", error);
+        }
+    }
+
+    static jvmtiFrameInfo* frameToInspect;
+    static std::string calledClass;
+
+    // Takes the full dex file for class 'classBeingRedefined'
+    // - isolates the dex code for the class out of the dex file
+    // - calls sTransformer.runTransformers on the isolated dex code
+    // - send the transformed code back to the runtime
+    static void
+    InspectClass(jvmtiEnv* jvmtiEnv,
+                 JNIEnv* env,
+                 jclass classBeingRedefined,
+                 jobject loader,
+                 const char* name,
+                 jobject protectionDomain,
+                 jint classDataLen,
+                 const unsigned char* classData,
+                 jint* newClassDataLen,
+                 unsigned char** newClassData) {
+        calledClass = "none";
+
+        Reader reader(classData, classDataLen);
+
+        char *calledMethodName;
+        char *calledMethodSignature;
+        jvmtiError error = jvmtiEnv->GetMethodName(frameToInspect->method, &calledMethodName,
+                                                   &calledMethodSignature, nullptr);
+        if (error != JVMTI_ERROR_NONE) {
+            return;
+        }
+
+        u4 index = reader.FindClassIndex(ClassNameToDescriptor(name).c_str());
+        reader.CreateClassIr(index);
+        std::shared_ptr<ir::DexFile> class_ir = reader.GetIr();
+
+        for (auto& method : class_ir->encoded_methods) {
+            if (Utf8Cmp(method->decl->name->c_str(), calledMethodName) == 0
+                && Utf8Cmp(method->decl->prototype->Signature().c_str(), calledMethodSignature) == 0) {
+                CodeIr method_ir(method.get(), class_ir);
+
+                for (auto instruction : method_ir.instructions) {
+                    Bytecode* bytecode = dynamic_cast<Bytecode*>(instruction);
+                    if (bytecode != nullptr && bytecode->offset == frameToInspect->location) {
+                        Method *method = bytecode->CastOperand<Method>(1);
+                        calledClass = method->ir_method->parent->Decl().c_str();
+
+                        goto exit;
+                    }
+                }
+            }
+        }
+
+        exit:
+        free(calledMethodName);
+        free(calledMethodSignature);
+    }
+
+#define GOTO_ON_ERROR(label) \
+    if (error != JVMTI_ERROR_NONE) { \
+        goto label; \
+    }
+
+// stack frame of the caller if method was called directly
+#define DIRECT_CALL_STACK_FRAME (6)
+
+// stack frame of the caller if method was called as 'real method'
+#define REALMETHOD_CALL_STACK_FRAME (23)
+
+    extern "C" JNIEXPORT jstring JNICALL
+    Java_com_android_dx_mockito_inline_StaticMockMethodAdvice_nativeGetCalledClassName(JNIEnv* env,
+                                                                            jclass klass,
+                                                                            jthread currentThread) {
+
+        JavaVM *vm;
+        jint jvmError = env->GetJavaVM(&vm);
+        if (jvmError != JNI_OK) {
+            return nullptr;
+        }
+
+        jvmtiEnv *jvmtiEnv;
+        jvmError = vm->GetEnv(reinterpret_cast<void**>(&jvmtiEnv), JVMTI_VERSION_1_2);
+        if (jvmError != JNI_OK) {
+            return nullptr;
+        }
+
+        jvmtiCapabilities caps;
+        memset(&caps, 0, sizeof(caps));
+        caps.can_retransform_classes = 1;
+
+        jvmtiError error = jvmtiEnv->AddCapabilities(&caps);
+        GOTO_ON_ERROR(unregister_env_and_exit);
+
+        jvmtiEventCallbacks cb;
+        memset(&cb, 0, sizeof(cb));
+        cb.ClassFileLoadHook = InspectClass;
+
+        jvmtiFrameInfo frameInfo[REALMETHOD_CALL_STACK_FRAME + 1];
+        jint numFrames;
+        error = jvmtiEnv->GetStackTrace(nullptr, 0, REALMETHOD_CALL_STACK_FRAME + 1, frameInfo,
+                                        &numFrames);
+        GOTO_ON_ERROR(unregister_env_and_exit);
+
+        // Method might be called directly or as 'real method' (see
+        // StaticMockMethodAdvice.SuperMethodCall#invoke). Hence the real caller might be in stack
+        // frame DIRECT_CALL_STACK_FRAME for a direct call or REALMETHOD_CALL_STACK_FRAME for a
+        // call through the 'real method' mechanism.
+        int callingFrameNum;
+        if (numFrames < REALMETHOD_CALL_STACK_FRAME) {
+            callingFrameNum = DIRECT_CALL_STACK_FRAME;
+        } else {
+            char *directCallMethodName;
+
+            jvmtiEnv->GetMethodName(frameInfo[DIRECT_CALL_STACK_FRAME].method,
+                                    &directCallMethodName, nullptr, nullptr);
+            if (strcmp(directCallMethodName, "invoke") == 0) {
+                callingFrameNum = REALMETHOD_CALL_STACK_FRAME;
+            } else {
+                callingFrameNum = DIRECT_CALL_STACK_FRAME;
+            }
+        }
+
+        jclass callingClass;
+        error = jvmtiEnv->GetMethodDeclaringClass(frameInfo[callingFrameNum].method, &callingClass);
+        GOTO_ON_ERROR(unregister_env_and_exit);
+
+        error = jvmtiEnv->SetEventCallbacks(&cb, sizeof(cb));
+        GOTO_ON_ERROR(unregister_env_and_exit);
+
+        error = jvmtiEnv->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_CLASS_FILE_LOAD_HOOK,
+                                                   currentThread);
+        GOTO_ON_ERROR(unset_cb_and_exit);
+
+        frameToInspect = &frameInfo[callingFrameNum];
+        error = jvmtiEnv->RetransformClasses(1, &callingClass);
+        GOTO_ON_ERROR(disable_hook_and_exit);
+
+        disable_hook_and_exit:
+        jvmtiEnv->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_CLASS_FILE_LOAD_HOOK,
+                                           currentThread);
+
+        unset_cb_and_exit:
+        memset(&cb, 0, sizeof(cb));
+        jvmtiEnv->SetEventCallbacks(&cb, sizeof(cb));
+
+        unregister_env_and_exit:
+        jvmtiEnv->DisposeEnvironment();
+
+        if (error != JVMTI_ERROR_NONE) {
+            return nullptr;
+        }
+
+        return env->NewStringUTF(calledClass.c_str());
+    }
+
+}  // namespace com_android_dx_mockito_inline
+
diff --git a/dexmaker-mockito-inline-tests/AndroidManifest.xml b/dexmaker-mockito-inline-tests/AndroidManifest.xml
deleted file mode 100644
index 44afd30..0000000
--- a/dexmaker-mockito-inline-tests/AndroidManifest.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-<manifest package="com.android.dexmaker.mockito.inline.tests">
-    <application />
-</manifest>
diff --git a/dexmaker-mockito-inline-tests/CMakeLists.txt b/dexmaker-mockito-inline-tests/CMakeLists.txt
new file mode 100644
index 0000000..32ce07e
--- /dev/null
+++ b/dexmaker-mockito-inline-tests/CMakeLists.txt
@@ -0,0 +1,33 @@
+cmake_minimum_required(VERSION 3.4.1)
+
+set(slicer_sources
+    ../dexmaker-mockito-inline/external/slicer/bytecode_encoder.cc
+    ../dexmaker-mockito-inline/external/slicer/code_ir.cc
+    ../dexmaker-mockito-inline/external/slicer/common.cc
+    ../dexmaker-mockito-inline/external/slicer/control_flow_graph.cc
+    ../dexmaker-mockito-inline/external/slicer/debuginfo_encoder.cc
+    ../dexmaker-mockito-inline/external/slicer/dex_bytecode.cc
+    ../dexmaker-mockito-inline/external/slicer/dex_format.cc
+    ../dexmaker-mockito-inline/external/slicer/dex_ir_builder.cc
+    ../dexmaker-mockito-inline/external/slicer/dex_ir.cc
+    ../dexmaker-mockito-inline/external/slicer/dex_utf8.cc
+    ../dexmaker-mockito-inline/external/slicer/instrumentation.cc
+    ../dexmaker-mockito-inline/external/slicer/reader.cc
+    ../dexmaker-mockito-inline/external/slicer/tryblocks_encoder.cc
+    ../dexmaker-mockito-inline/external/slicer/writer.cc)
+
+add_library(slicer
+            STATIC
+            ${slicer_sources})
+
+include_directories(../dexmaker-mockito-inline/external/jdk ../dexmaker-mockito-inline/external/slicer/export/)
+
+target_link_libraries(slicer z)
+
+add_library(multiplejvmtiagentsinterferenceagent
+            SHARED
+            src/main/jni/multiplejvmtiagentsinterferenceagent/agent.cc)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DANDROID_STL=c++_shared -frtti -Wall -Werror -Wno-unused-parameter -Wno-shift-count-overflow -Wno-error=non-virtual-dtor -Wno-sign-compare -Wno-switch -Wno-missing-braces")
+
+target_link_libraries(multiplejvmtiagentsinterferenceagent slicer)
diff --git a/dexmaker-mockito-inline-tests/build.gradle b/dexmaker-mockito-inline-tests/build.gradle
new file mode 100644
index 0000000..c731115
--- /dev/null
+++ b/dexmaker-mockito-inline-tests/build.gradle
@@ -0,0 +1,54 @@
+buildscript {
+    repositories {
+        maven {
+            url "https://plugins.gradle.org/m2/"
+        }
+    }
+    dependencies {
+        classpath "net.ltgt.gradle:gradle-errorprone-plugin:0.0.13"
+    }
+}
+
+apply plugin: "net.ltgt.errorprone"
+apply plugin: 'com.android.library'
+
+android {
+    compileSdkVersion 28
+    buildToolsVersion '28.0.3'
+
+    android {
+        lintOptions {
+            disable 'InvalidPackage'
+            warning 'NewApi'
+        }
+    }
+
+    defaultConfig {
+        minSdkVersion 28
+        targetSdkVersion 28
+        versionName VERSION_NAME
+
+        testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
+    }
+
+    externalNativeBuild {
+        cmake {
+            path 'CMakeLists.txt'
+        }
+    }
+}
+
+repositories {
+    jcenter()
+    google()
+}
+
+dependencies {
+    implementation project(':dexmaker-mockito-tests')
+    compileOnly project(':dexmaker-mockito-inline')
+    androidTestImplementation project(':dexmaker-mockito-inline')
+
+    implementation 'junit:junit:4.12'
+    implementation 'com.android.support.test:runner:1.0.2'
+    api 'org.mockito:mockito-core:2.21.0', { exclude group: 'net.bytebuddy' }
+}
diff --git a/dexmaker-mockito-inline-tests/src/androidTest/java/com/android/dx/mockito/tests b/dexmaker-mockito-inline-tests/src/androidTest/java/com/android/dx/mockito/tests
deleted file mode 120000
index f13e9f0..0000000
--- a/dexmaker-mockito-inline-tests/src/androidTest/java/com/android/dx/mockito/tests
+++ /dev/null
@@ -1 +0,0 @@
-../../../../../../../../dexmaker-mockito-tests/src/androidTest/java/com/android/dx/mockito/tests
\ No newline at end of file
diff --git a/dexmaker-mockito-inline-tests/src/main/AndroidManifest.xml b/dexmaker-mockito-inline-tests/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..9f14dbf
--- /dev/null
+++ b/dexmaker-mockito-inline-tests/src/main/AndroidManifest.xml
@@ -0,0 +1,6 @@
+<manifest xmlns:tools="http://schemas.android.com/tools"
+    package="com.android.dexmaker.mockito.inline.tests"
+    xmlns:android="http://schemas.android.com/apk/res/android">
+    <application android:debuggable="true"
+        tools:ignore="HardcodedDebugMode" />
+</manifest>
diff --git a/dexmaker-mockito-inline-tests/src/androidTest/java/com/android/dx/mockito/inline/tests/MockFinal.java b/dexmaker-mockito-inline-tests/src/main/java/com/android/dx/mockito/inline/tests/MockFinal.java
similarity index 98%
rename from dexmaker-mockito-inline-tests/src/androidTest/java/com/android/dx/mockito/inline/tests/MockFinal.java
rename to dexmaker-mockito-inline-tests/src/main/java/com/android/dx/mockito/inline/tests/MockFinal.java
index fa02471..03ac992 100644
--- a/dexmaker-mockito-inline-tests/src/androidTest/java/com/android/dx/mockito/inline/tests/MockFinal.java
+++ b/dexmaker-mockito-inline-tests/src/main/java/com/android/dx/mockito/inline/tests/MockFinal.java
@@ -16,15 +16,6 @@
 
 package com.android.dx.mockito.inline.tests;
 
-import android.content.Intent;
-import android.os.IBinder;
-import android.print.PrintAttributes;
-import android.printservice.PrintService;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
@@ -33,6 +24,16 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
+import android.content.Intent;
+import android.os.IBinder;
+import android.print.PrintAttributes;
+import android.printservice.PrintService;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 @RunWith(AndroidJUnit4.class)
 public class MockFinal {
     @Test
@@ -73,7 +74,7 @@
         assertSame(fakeBinder, mockService.onBind(new Intent()));
     }
 
-    private final class FinalNonDefaultConstructorClass {
+    private static final class FinalNonDefaultConstructorClass {
         public FinalNonDefaultConstructorClass(int i) {
         }
 
@@ -121,6 +122,7 @@
     }
 
     private static final class SubClass extends SuperClass {
+        @Override
         String returnC() {
             return "subC";
         }
diff --git a/dexmaker-mockito-inline-tests/src/androidTest/java/com/android/dx/mockito/inline/tests/MockNonPublic.java b/dexmaker-mockito-inline-tests/src/main/java/com/android/dx/mockito/inline/tests/MockNonPublic.java
similarity index 97%
rename from dexmaker-mockito-inline-tests/src/androidTest/java/com/android/dx/mockito/inline/tests/MockNonPublic.java
rename to dexmaker-mockito-inline-tests/src/main/java/com/android/dx/mockito/inline/tests/MockNonPublic.java
index aa828e5..60b2845 100644
--- a/dexmaker-mockito-inline-tests/src/androidTest/java/com/android/dx/mockito/inline/tests/MockNonPublic.java
+++ b/dexmaker-mockito-inline-tests/src/main/java/com/android/dx/mockito/inline/tests/MockNonPublic.java
@@ -108,6 +108,7 @@
     }
 
     private static class PrivateClass implements SingleMethodInterface {
+        @Override
         public String returnA() {
             return "A";
         }
@@ -129,6 +130,7 @@
     }
 
     private interface PrivateInterface extends SingleMethodInterface {
+        @Override
         String returnA();
     }
 
@@ -138,6 +140,7 @@
     }
 
     private static class SubOfPrivateInterface implements PrivateInterface {
+        @Override
         public String returnA() {
             return "A";
         }
@@ -159,10 +162,12 @@
     }
 
     private static abstract class PrivateAbstractClass implements DualMethodInterface {
+        @Override
         public String returnA() {
             return "A";
         }
 
+        @Override
         public abstract String returnB();
     }
 
@@ -172,6 +177,7 @@
     }
 
     private static class SubOfPrivateAbstractClass extends PrivateAbstractClass {
+        @Override
         public String returnB() {
             return "B";
         }
@@ -193,6 +199,7 @@
     }
 
     static class PackagePrivateClass implements SingleMethodInterface {
+        @Override
         public String returnA() {
             return "A";
         }
@@ -204,10 +211,12 @@
     }
 
     static abstract class PackagePrivateAbstractClass implements DualMethodInterface {
+        @Override
         public String returnA() {
             return "A";
         }
 
+        @Override
         public abstract String returnB();
     }
 
@@ -217,6 +226,7 @@
     }
 
     static class SubOfPackagePrivateAbstractClass extends PackagePrivateAbstractClass {
+        @Override
         public String returnB() {
             return "B";
         }
@@ -238,6 +248,7 @@
     }
 
     interface PackagePrivateInterface extends SingleMethodInterface {
+        @Override
         String returnA();
     }
 
@@ -247,6 +258,7 @@
     }
 
     static class SubOfPackagePrivateInterface implements PackagePrivateInterface {
+        @Override
         public String returnA() {
             return "A";
         }
@@ -331,6 +343,7 @@
 
     public static class SubOfAbstractClassWithPackagePrivateMethod extends
             AbstractClassWithPackagePrivateMethod {
+        @Override
         String returnB() {
             return "B";
         }
diff --git a/dexmaker-mockito-inline-tests/src/androidTest/java/com/android/dx/mockito/inline/tests/MultipleJvmtiAgentsInterference.java b/dexmaker-mockito-inline-tests/src/main/java/com/android/dx/mockito/inline/tests/MultipleJvmtiAgentsInterference.java
similarity index 93%
rename from dexmaker-mockito-inline-tests/src/androidTest/java/com/android/dx/mockito/inline/tests/MultipleJvmtiAgentsInterference.java
rename to dexmaker-mockito-inline-tests/src/main/java/com/android/dx/mockito/inline/tests/MultipleJvmtiAgentsInterference.java
index 4f84276..bfc12fb 100644
--- a/dexmaker-mockito-inline-tests/src/androidTest/java/com/android/dx/mockito/inline/tests/MultipleJvmtiAgentsInterference.java
+++ b/dexmaker-mockito-inline-tests/src/main/java/com/android/dx/mockito/inline/tests/MultipleJvmtiAgentsInterference.java
@@ -16,7 +16,6 @@
 
 package com.android.dx.mockito.inline.tests;
 
-import android.os.Build;
 import android.os.Debug;
 
 import org.junit.AfterClass;
@@ -24,13 +23,12 @@
 import org.junit.Test;
 
 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 = "libmultiplejvmtiagentsinterferenceagent.so";
 
-    public class TestClass {
+    public static class TestClass {
         public String returnA() {
             return "A";
         }
@@ -38,8 +36,6 @@
 
     @BeforeClass
     public static void installTestAgent() throws Exception {
-        assumeTrue(Build.VERSION.SDK_INT >= 28);
-
         Debug.attachJvmtiAgent(AGENT_LIB_NAME, null,
                 MultipleJvmtiAgentsInterference.class.getClassLoader());
     }
diff --git a/dexmaker-mockito-inline/CMakeLists.txt b/dexmaker-mockito-inline/CMakeLists.txt
index b700cd4..1c0fa81 100644
--- a/dexmaker-mockito-inline/CMakeLists.txt
+++ b/dexmaker-mockito-inline/CMakeLists.txt
@@ -28,6 +28,6 @@
             SHARED
             src/main/jni/dexmakerjvmtiagent/agent.cc)
 
-set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -frtti -Wall -Werror -Wno-unused-parameter -Wno-shift-count-overflow -Wno-error=non-virtual-dtor -Wno-sign-compare -Wno-switch -Wno-missing-braces")
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DANDROID_STL=c++_shared -frtti -Wall -Werror -Wno-unused-parameter -Wno-shift-count-overflow -Wno-error=non-virtual-dtor -Wno-sign-compare -Wno-switch -Wno-missing-braces")
 
 target_link_libraries(dexmakerjvmtiagent slicer)
diff --git a/dexmaker-mockito-inline/build.gradle b/dexmaker-mockito-inline/build.gradle
index 853f061..8ca3d3c 100644
--- a/dexmaker-mockito-inline/build.gradle
+++ b/dexmaker-mockito-inline/build.gradle
@@ -1,16 +1,36 @@
+buildscript {
+    repositories {
+        maven {
+            url "https://plugins.gradle.org/m2/"
+        }
+    }
+    dependencies {
+        classpath "net.ltgt.gradle:gradle-errorprone-plugin:0.0.13"
+    }
+}
+
+apply plugin: "net.ltgt.errorprone"
 apply plugin: 'com.android.library'
+apply plugin: 'maven-publish'
+apply plugin: 'ivy-publish'
+apply plugin: 'com.jfrog.artifactory'
+
+version = VERSION_NAME
 
 android {
-    compileSdkVersion 'android-P'
-    buildToolsVersion "25.0.0"
+    compileSdkVersion 28
+    buildToolsVersion '28.0.3'
 
-    lintOptions {
-        abortOnError false
+    android {
+        lintOptions {
+            disable 'InvalidPackage'
+            warning 'NewApi'
+        }
     }
 
     defaultConfig {
-        minSdkVersion 25
-        targetSdkVersion 25
+        minSdkVersion 1
+        targetSdkVersion 28
         versionName VERSION_NAME
     }
 
@@ -19,15 +39,80 @@
             path 'CMakeLists.txt'
         }
     }
+}
 
+tasks.withType(JavaCompile) {
+    options.compilerArgs += ["-Xep:StringSplitter:OFF"]
+}
+
+task sourcesJar(type: Jar) {
+    classifier = 'sources'
+    from android.sourceSets.main.java.srcDirs
+}
+
+task javadoc(type: Javadoc) {
+    source = android.sourceSets.main.java.srcDirs
+    classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
+}
+
+task javadocJar(type: Jar, dependsOn: javadoc) {
+    classifier = 'javadoc'
+    from javadoc.destinationDir
+}
+
+publishing {
+    publications {
+        ivyLib(IvyPublication) {
+          from new org.gradle.api.internal.java.JavaLibrary(new org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact(project.getName(), 'aar', 'aar', null, new Date(), new File("$buildDir/outputs/aar/${project.getName()}-release.aar"), assemble), project.configurations.implementation.getAllDependencies())
+          artifact sourcesJar
+          artifact javadocJar
+        }
+
+        lib(MavenPublication) {
+          from new org.gradle.api.internal.java.JavaLibrary(new org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact(project.getName(), 'aar', 'aar', null, new Date(), new File("$buildDir/outputs/aar/${project.getName()}-release.aar"), assemble), project.configurations.implementation.getAllDependencies())
+
+            artifact sourcesJar
+            artifact javadocJar
+
+            pom.withXml {
+                asNode().children().last() + {
+                    resolveStrategy = Closure.DELEGATE_FIRST
+                    description = 'Implementation of the Mockito Inline API for use on the Android Dalvik VM'
+                    url 'https://github.com/linkedin/dexmaker'
+                    scm {
+                        url 'https://github.com/linkedin/dexmaker'
+                        connection 'scm:git:git://github.com/linkedin/dexmaker.git'
+                        developerConnection 'https://github.com/linkedin/dexmaker.git'
+                    }
+                    licenses {
+                        license {
+                            name 'The Apache Software License, Version 2.0'
+                            url 'http://www.apache.org/license/LICENSE-2.0.txt'
+                            distribution 'repo'
+                        }
+                    }
+
+                    developers {
+                        developer {
+                            id 'com.linkedin'
+                            name 'LinkedIn Corp'
+                            email ''
+                        }
+                    }
+                }
+            }
+        }
+    }
 }
 
 repositories {
     jcenter()
+    google()
 }
 
 dependencies {
-    compile project(':dexmaker')
-    compile 'org.mockito:mockito-core:2.15.0', { exclude group: "net.bytebuddy" }
+    implementation project(':dexmaker')
+
+    implementation 'org.mockito:mockito-core:2.21.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 c702b2f..89f713a 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
@@ -26,7 +26,6 @@
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Map;
-import java.util.Random;
 import java.util.Set;
 
 /**
@@ -59,7 +58,6 @@
                     Float.class,
                     Double.class,
                     String.class));
-    private final static Random random = new Random();
 
     /** Jvmti agent responsible for triggering transformation s*/
     private final JvmtiAgent agent;
@@ -99,7 +97,7 @@
                      Map<Object, InvocationHandlerAdapter> mocks) {
         this.agent = agent;
         mockedTypes = Collections.synchronizedSet(new HashSet<Class<?>>());
-        identifier = Long.toString(random.nextLong());
+        identifier = String.valueOf(System.identityHashCode(this));
         MockMethodAdvice advice = new MockMethodAdvice(mocks);
 
         try {
diff --git a/dexmaker-mockito-inline/src/main/java/com/android/dx/mockito/inline/DexmakerStackTraceCleaner.java b/dexmaker-mockito-inline/src/main/java/com/android/dx/mockito/inline/DexmakerStackTraceCleaner.java
index 883f77b..5cb3817 100644
--- a/dexmaker-mockito-inline/src/main/java/com/android/dx/mockito/inline/DexmakerStackTraceCleaner.java
+++ b/dexmaker-mockito-inline/src/main/java/com/android/dx/mockito/inline/DexmakerStackTraceCleaner.java
@@ -40,7 +40,8 @@
                         && !(className.startsWith("com.android.dx.mockito.")
                              // Do not clean unit tests
                              && !className.startsWith("com.android.dx.mockito.tests")
-                             && !className.startsWith("com.android.dx.mockito.inline.tests"))
+                             && !className.startsWith("com.android.dx.mockito.inline.tests")
+                             && !className.startsWith("com.android.dx.mockito.inline.extended.tests"))
 
                         // dalvik interface proxies
                         && !className.startsWith("$Proxy")
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 abcf4f5..fc2a641 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
@@ -24,12 +24,12 @@
 import com.android.dx.stock.ProxyBuilder.MethodSetEntry;
 
 import org.mockito.Mockito;
+import org.mockito.creation.instance.Instantiator;
 import org.mockito.exceptions.base.MockitoException;
-import org.mockito.internal.creation.instance.Instantiator;
 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.InstantiatorProvider2;
 import org.mockito.plugins.MockMaker;
 
 import java.io.IOException;
@@ -53,6 +53,7 @@
  *
  * <p>This is done by transforming the byte code of the classes to add method entry hooks.
  */
+
 public final class InlineDexmakerMockMaker implements MockMaker {
     private static final String DISPATCHER_CLASS_NAME =
             "com.android.dx.mockito.inline.MockMethodDispatcher";
@@ -68,7 +69,13 @@
      * Class injected into the bootstrap classloader. All entry hooks added to methods will call
      * this class.
      */
-    private static final Class DISPATCHER_CLASS;
+    public static final Class DISPATCHER_CLASS;
+
+    /**
+     * {@code ExtendedMockito#spyOn} allows to turn an existing object into a spy. If this operation
+     * is running this field is set to the object that should become a spy.
+     */
+    public static ThreadLocal<Object> onSpyInProgressInstance = new ThreadLocal<>();
 
     /*
      * One time setup to allow the system to mocking via this mock maker.
@@ -260,14 +267,20 @@
             Class<? extends T> proxyClass;
 
             Instantiator instantiator = Mockito.framework().getPlugins()
-                    .getDefaultPlugin(InstantiatorProvider.class).getInstantiator(settings);
+                    .getDefaultPlugin(InstantiatorProvider2.class).getInstantiator(settings);
 
             if (subclassingRequired) {
                 try {
                     // support abstract methods via dexmaker's ProxyBuilder
-                    proxyClass = ProxyBuilder.forClass(typeToMock).implementing(extraInterfaces)
-                            .onlyMethods(getMethodsToProxy(settings)).withSharedClassLoader()
-                            .buildProxyClass();
+                    ProxyBuilder builder = ProxyBuilder.forClass(typeToMock).implementing
+                            (extraInterfaces)
+                            .onlyMethods(getMethodsToProxy(settings)).withSharedClassLoader();
+
+                    if (Build.VERSION.SDK_INT >= 28) {
+                        builder.markTrusted();
+                    }
+
+                    proxyClass = builder.buildProxyClass();
                 } catch (RuntimeException e) {
                     throw e;
                 } catch (Exception e) {
@@ -276,18 +289,23 @@
 
                 try {
                     mock = instantiator.newInstance(proxyClass);
-                } catch (org.mockito.internal.creation.instance.InstantiationException e) {
+                } catch (org.mockito.creation.instance.InstantiationException e) {
                     throw new MockitoException("Unable to create mock instance of type '"
                             + proxyClass.getSuperclass().getSimpleName() + "'", e);
                 }
 
                 ProxyBuilder.setInvocationHandler(mock, handlerAdapter);
             } else {
-                try {
-                    mock = instantiator.newInstance(typeToMock);
-                } catch (org.mockito.internal.creation.instance.InstantiationException e) {
-                    throw new MockitoException("Unable to create mock instance of type '"
-                            + typeToMock.getSimpleName() + "'", e);
+                if (settings.getSpiedInstance() != null
+                        && onSpyInProgressInstance.get() == settings.getSpiedInstance()) {
+                    mock = (T) onSpyInProgressInstance.get();
+                } else {
+                    try {
+                        mock = instantiator.newInstance(typeToMock);
+                    } catch (org.mockito.creation.instance.InstantiationException e) {
+                        throw new MockitoException("Unable to create mock instance of type '"
+                                + typeToMock.getSimpleName() + "'", e);
+                    }
                 }
             }
         }
@@ -358,7 +376,7 @@
         private static final int MAX_GET_WITHOUT_CLEAN = 16384;
 
         private final Object lock = new Object();
-        private static StrongKey cachedKey;
+        private StrongKey cachedKey;
 
         private HashMap<WeakKey, InvocationHandlerAdapter> adapters = new HashMap<>();
 
@@ -424,6 +442,7 @@
             return adapters.isEmpty();
         }
 
+        @SuppressWarnings("CollectionIncompatibleType")
         @Override
         public boolean containsKey(Object mock) {
             synchronized (lock) {
@@ -442,6 +461,7 @@
             }
         }
 
+        @SuppressWarnings("CollectionIncompatibleType")
         @Override
         public InvocationHandlerAdapter get(Object mock) {
             synchronized (lock) {
@@ -504,6 +524,7 @@
             }
         }
 
+        @SuppressWarnings("CollectionIncompatibleType")
         @Override
         public InvocationHandlerAdapter remove(Object mock) {
             synchronized (lock) {
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 34c5c48..84d22e3 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
@@ -51,8 +51,9 @@
      * @throws IOException If jvmti could not be enabled or agent could not be loaded
      */
     JvmtiAgent() throws IOException {
-        if (Build.VERSION.SDK_INT < 28) {
-            throw new IOException("Requires API 28. API is " + Build.VERSION.SDK_INT);
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
+            throw new IOException("Requires API level " + Build.VERSION_CODES.P + ". API level is "
+                    + Build.VERSION.SDK_INT);
         }
 
         ClassLoader cl = JvmtiAgent.class.getClassLoader();
diff --git a/dexmaker-mockito-inline/src/main/java/com/android/dx/mockito/inline/MockMakerMultiplexer.java b/dexmaker-mockito-inline/src/main/java/com/android/dx/mockito/inline/MockMakerMultiplexer.java
new file mode 100644
index 0000000..c5f8db6
--- /dev/null
+++ b/dexmaker-mockito-inline/src/main/java/com/android/dx/mockito/inline/MockMakerMultiplexer.java
@@ -0,0 +1,106 @@
+/*
+ * 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.inline;
+
+import android.util.Log;
+
+import org.mockito.invocation.MockHandler;
+import org.mockito.mock.MockCreationSettings;
+import org.mockito.plugins.MockMaker;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+
+/**
+ * Multiplexes multiple mock makers
+ */
+public final class MockMakerMultiplexer implements MockMaker {
+    private static final String LOG_TAG = MockMakerMultiplexer.class.getSimpleName();
+    private final static MockMaker[] MOCK_MAKERS;
+
+    static {
+        String[] potentialMockMakers = new String[] {
+                "com.android.dx.mockito.inline.InlineStaticMockMaker",
+                InlineDexmakerMockMaker.class.getName()
+        };
+
+        ArrayList<MockMaker> mockMakers = new ArrayList<>();
+        for (String potentialMockMaker : potentialMockMakers) {
+            try {
+                Class<? extends MockMaker> mockMakerClass = (Class<? extends MockMaker>)
+                        Class.forName(potentialMockMaker);
+                mockMakers.add(mockMakerClass.getDeclaredConstructor().newInstance());
+            } catch (ClassNotFoundException | InstantiationException | IllegalAccessException
+                    | NoSuchMethodException | InvocationTargetException e) {
+                if (potentialMockMaker.equals(InlineDexmakerMockMaker.class.getName())) {
+                    Log.e(LOG_TAG, "Could not init mockmaker " + potentialMockMaker, e);
+                } else {
+                    // Additional mock makers might not be loaded
+                    Log.e(LOG_TAG, "Could not init mockmaker " + potentialMockMaker);
+                }
+            }
+        }
+
+        MOCK_MAKERS = mockMakers.toArray(new MockMaker[]{});
+    }
+
+    @Override
+    public <T> T createMock(MockCreationSettings<T> settings, MockHandler handler) {
+        for (MockMaker mockMaker : MOCK_MAKERS) {
+            T mock = mockMaker.createMock(settings, handler);
+
+            if (mock != null) {
+                return mock;
+            }
+        }
+
+        return null;
+    }
+
+    @Override
+    public MockHandler getHandler(Object mock) {
+        for (MockMaker mockMaker : MOCK_MAKERS) {
+            MockHandler handler = mockMaker.getHandler(mock);
+
+            if (handler != null) {
+                return handler;
+            }
+        }
+
+        return null;
+    }
+
+    @Override
+    public void resetMock(Object mock, MockHandler newHandler, MockCreationSettings settings) {
+        for (MockMaker mockMaker : MOCK_MAKERS) {
+            mockMaker.resetMock(mock, newHandler, settings);
+        }
+    }
+
+    @Override
+    public TypeMockability isTypeMockable(Class<?> type) {
+        for (MockMaker mockMaker : MOCK_MAKERS) {
+            TypeMockability mockability = mockMaker.isTypeMockable(type);
+
+            if (mockability != null) {
+                return mockability;
+            }
+        }
+
+        return null;
+    }
+}
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 dfe242f..74e24b0 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,6 +5,7 @@
 
 package com.android.dx.mockito.inline;
 
+import java.lang.ref.WeakReference;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
@@ -24,6 +25,7 @@
     /** Pattern to decompose a instrumentedMethodWithTypeAndSignature */
     private final Pattern methodPattern = Pattern.compile("(.*)#(.*)\\((.*)\\)");
 
+    @SuppressWarnings("ThreadLocalUsage")
     private final SelfCallInfo selfCallInfo = new SelfCallInfo();
 
     MockMethodAdvice(Map<Object, InvocationHandlerAdapter> interceptors) {
@@ -255,14 +257,14 @@
     private static class SuperMethodCall implements InvocationHandlerAdapter.SuperMethod {
         private final SelfCallInfo selfCallInfo;
         private final Method origin;
-        private final Object instance;
+        private final WeakReference<Object> instance;
         private final Object[] arguments;
 
         private SuperMethodCall(SelfCallInfo selfCallInfo, Method origin, Object instance,
                                 Object[] arguments) {
             this.selfCallInfo = selfCallInfo;
             this.origin = origin;
-            this.instance = instance;
+            this.instance = new WeakReference<>(instance);
             this.arguments = arguments;
         }
 
@@ -281,8 +283,8 @@
 
             // By setting instance in the the selfCallInfo, once single method call on this instance
             // and thread will call the read method as isMocked will return false.
-            selfCallInfo.set(instance);
-            return tryInvoke(origin, instance, arguments);
+            selfCallInfo.set(instance.get());
+            return tryInvoke(origin, instance.get(), arguments);
         }
 
     }
diff --git a/dexmaker-mockito-inline/src/main/resources/README.txt b/dexmaker-mockito-inline/src/main/resources/README.txt
index b94264b..fcf183f 100644
--- a/dexmaker-mockito-inline/src/main/resources/README.txt
+++ b/dexmaker-mockito-inline/src/main/resources/README.txt
@@ -1,2 +1,7 @@
 dispatcher.jar is the classes.dex of the apk created by dexmaker-mockito-inline-dispatcher
-repackaged into a jar. We should automate this.
\ No newline at end of file
+repackaged into a jar. We should automate this.
+
+unzip dexmaker-mockito-inline-dispatcher/build/outputs/apk/release/dexmaker-mockito-inline
+-dispatcher-release-unsigned.apk classes.dex
+jar -cf dexmaker-mockito-inline/src/main/resources/dispatcher.jar classes.dex
+rm classes.dex
diff --git a/dexmaker-mockito-inline/src/main/resources/dispatcher.jar b/dexmaker-mockito-inline/src/main/resources/dispatcher.jar
index 6a5cf5f..762c153 100644
--- a/dexmaker-mockito-inline/src/main/resources/dispatcher.jar
+++ b/dexmaker-mockito-inline/src/main/resources/dispatcher.jar
Binary files differ
diff --git a/dexmaker-mockito-inline/src/main/resources/mockito-extensions/org.mockito.plugins.MockMaker b/dexmaker-mockito-inline/src/main/resources/mockito-extensions/org.mockito.plugins.MockMaker
index 4f2b284..15d3297 100644
--- a/dexmaker-mockito-inline/src/main/resources/mockito-extensions/org.mockito.plugins.MockMaker
+++ b/dexmaker-mockito-inline/src/main/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -1 +1 @@
-com.android.dx.mockito.inline.InlineDexmakerMockMaker
\ No newline at end of file
+com.android.dx.mockito.inline.MockMakerMultiplexer
diff --git a/dexmaker-mockito-tests/AndroidManifest.xml b/dexmaker-mockito-tests/AndroidManifest.xml
deleted file mode 100644
index 45201f9..0000000
--- a/dexmaker-mockito-tests/AndroidManifest.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-<manifest package="com.android.dexmaker.mockito.tests">
-    <application />
-</manifest>
diff --git a/dexmaker-mockito-tests/build.gradle b/dexmaker-mockito-tests/build.gradle
index a08e254..f051535 100644
--- a/dexmaker-mockito-tests/build.gradle
+++ b/dexmaker-mockito-tests/build.gradle
@@ -1,20 +1,33 @@
-apply plugin: 'com.android.application'
+buildscript {
+    repositories {
+        maven {
+            url "https://plugins.gradle.org/m2/"
+        }
+    }
+    dependencies {
+        classpath "net.ltgt.gradle:gradle-errorprone-plugin:0.0.13"
+    }
+}
+
+apply plugin: "net.ltgt.errorprone"
+apply plugin: 'com.android.library'
 
 android {
-    compileSdkVersion 25
-    buildToolsVersion "25.0.0"
+    compileSdkVersion 28
 
-    lintOptions {
-        abortOnError false
+    android {
+        lintOptions {
+            disable 'InvalidPackage'
+            disable 'NewApi'
+        }
     }
 
     defaultConfig {
-        applicationId "com.android.dexmaker.mockito.tests"
-        minSdkVersion 25
-        targetSdkVersion 25
+        minSdkVersion 8
+        targetSdkVersion 28
         versionName VERSION_NAME
 
-        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+        testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
     }
 }
 
@@ -24,9 +37,10 @@
 }
 
 dependencies {
-    compile project(':dexmaker')
-    compile project(':dexmaker-mockito')
+    compileOnly project(':dexmaker-mockito')
+    androidTestImplementation project(':dexmaker-mockito')
 
-    androidTestCompile 'com.android.support.test:runner:0.5'
-    androidTestCompile 'junit:junit:4.12'
+    implementation 'com.android.support.test:runner:0.5'
+    implementation 'junit:junit:4.12'
+    api 'org.mockito:mockito-core:2.21.0', { exclude group: 'net.bytebuddy' }
 }
diff --git a/dexmaker-mockito-tests/src/main/AndroidManifest.xml b/dexmaker-mockito-tests/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..2f3d475
--- /dev/null
+++ b/dexmaker-mockito-tests/src/main/AndroidManifest.xml
@@ -0,0 +1,7 @@
+<manifest xmlns:tools="http://schemas.android.com/tools"
+    package="com.android.dexmaker.mockito.tests"
+    xmlns:android="http://schemas.android.com/apk/res/android">
+    <application android:debuggable="true"
+        tools:ignore="HardcodedDebugMode"/>
+</manifest>
+
diff --git a/dexmaker-mockito-tests/src/androidTest/java/com/android/dx/mockito/tests/BlacklistedApis.java b/dexmaker-mockito-tests/src/main/java/com/android/dx/mockito/tests/BlacklistedApis.java
similarity index 88%
rename from dexmaker-mockito-tests/src/androidTest/java/com/android/dx/mockito/tests/BlacklistedApis.java
rename to dexmaker-mockito-tests/src/main/java/com/android/dx/mockito/tests/BlacklistedApis.java
index ffe55fb..4a345b3 100644
--- a/dexmaker-mockito-tests/src/androidTest/java/com/android/dx/mockito/tests/BlacklistedApis.java
+++ b/dexmaker-mockito-tests/src/main/java/com/android/dx/mockito/tests/BlacklistedApis.java
@@ -23,16 +23,21 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.os.Build;
 import android.provider.Settings;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.AndroidJUnit4;
 import android.view.View;
 import android.widget.FrameLayout;
 
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -41,6 +46,11 @@
  */
 @RunWith(AndroidJUnit4.class)
 public class BlacklistedApis {
+    @Before
+    public void onlyRunOnPlatformsThatSupportBlacklisting() {
+        assumeTrue(Build.VERSION.SDK_INT >= 28);
+    }
+
     /**
      * Check if the application is marked as {@code android:debuggable} in the manifest
      *
@@ -55,6 +65,7 @@
     }
 
     @Test
+    @Ignore("b/117829328")
     public void callBlacklistedPublicMethodRealMethod() throws Exception {
         Context targetContext = InstrumentationRegistry.getTargetContext();
 
@@ -84,6 +95,7 @@
 
     @Test
     public void copyBlacklistedFields() throws Exception {
+        // Can only copy blacklisted fields when debuggable
         assumeTrue(isDebuggable());
 
         Context targetContext = InstrumentationRegistry.getTargetContext();
@@ -105,6 +117,7 @@
         parent.measure(100, 100);
     }
 
+    @SuppressLint({"PrivateApi", "CheckReturnValue"})
     @Test
     public void cannotCallBlackListedAfterSpying() {
         // Spying and mocking might change the View class's byte code
@@ -120,8 +133,9 @@
         }
     }
 
-    public class CallBlackListedMethod {
-        public boolean callingBlacklistedMethodCausesException() {
+    public static class CallBlackListedMethod {
+        @SuppressLint("PrivateApi")
+        boolean callingBlacklistedMethodCausesException() {
             // Settings.Global#isValidZenMode is a blacklisted method. Resolving it should fail
             try {
                 Settings.Global.class.getDeclaredMethod("isValidZenMode", Integer.TYPE);
@@ -138,7 +152,8 @@
         assertTrue(t.callingBlacklistedMethodCausesException());
     }
 
-    public abstract class CallBlacklistedMethodAbstract {
+    public static abstract class CallBlacklistedMethodAbstract {
+        @SuppressLint("PrivateApi")
         public boolean callingBlacklistedMethodCausesException() {
             // Settings.Global#isValidZenMode is a blacklisted method. Resolving it should fail
             try {
diff --git a/dexmaker-mockito-tests/src/androidTest/java/com/android/dx/mockito/tests/CleanStackTrace.java b/dexmaker-mockito-tests/src/main/java/com/android/dx/mockito/tests/CleanStackTrace.java
similarity index 100%
rename from dexmaker-mockito-tests/src/androidTest/java/com/android/dx/mockito/tests/CleanStackTrace.java
rename to dexmaker-mockito-tests/src/main/java/com/android/dx/mockito/tests/CleanStackTrace.java
diff --git a/dexmaker-mockito-tests/src/androidTest/java/com/android/dx/mockito/tests/GeneralMocking.java b/dexmaker-mockito-tests/src/main/java/com/android/dx/mockito/tests/GeneralMocking.java
similarity index 96%
rename from dexmaker-mockito-tests/src/androidTest/java/com/android/dx/mockito/tests/GeneralMocking.java
rename to dexmaker-mockito-tests/src/main/java/com/android/dx/mockito/tests/GeneralMocking.java
index 36d5612..4ee5172 100644
--- a/dexmaker-mockito-tests/src/androidTest/java/com/android/dx/mockito/tests/GeneralMocking.java
+++ b/dexmaker-mockito-tests/src/main/java/com/android/dx/mockito/tests/GeneralMocking.java
@@ -16,20 +16,7 @@
 
 package com.android.dx.mockito.tests;
 
-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;
@@ -41,6 +28,13 @@
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.exceptions.base.MockitoException;
+import org.mockito.exceptions.verification.NoInteractionsWanted;
+
 @RunWith(AndroidJUnit4.class)
 public class GeneralMocking {
     public static class TestClass {
diff --git a/dexmaker-mockito-tests/src/main/java/com/android/dx/mockito/tests/PartialClasses.java b/dexmaker-mockito-tests/src/main/java/com/android/dx/mockito/tests/PartialClasses.java
new file mode 100644
index 0000000..be9d790
--- /dev/null
+++ b/dexmaker-mockito-tests/src/main/java/com/android/dx/mockito/tests/PartialClasses.java
@@ -0,0 +1,65 @@
+/*
+ * 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 org.junit.Test;
+import org.mockito.exceptions.base.MockitoException;
+
+import java.util.AbstractList;
+
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests what happens if code tries to call real methods of abstract classed and in interfaces
+ */
+public class PartialClasses {
+    @Test
+    public void callRealMethodOnInterface() {
+        Runnable r = mock(Runnable.class);
+
+        try {
+            doCallRealMethod().when(r).run();
+            fail();
+        } catch (MockitoException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void callAbstractRealMethodOnAbstractClass() {
+        AbstractList l = mock(AbstractList.class);
+
+        try {
+            when(l.size()).thenCallRealMethod();
+            fail();
+        } catch (MockitoException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void callRealMethodOnAbstractClass() {
+        AbstractList l = mock(AbstractList.class);
+
+        doCallRealMethod().when(l).clear();
+
+        l.clear();
+    }
+}
diff --git a/dexmaker-mockito-tests/src/main/java/com/android/dx/mockito/tests/Stress.java b/dexmaker-mockito-tests/src/main/java/com/android/dx/mockito/tests/Stress.java
new file mode 100644
index 0000000..573ed67
--- /dev/null
+++ b/dexmaker-mockito-tests/src/main/java/com/android/dx/mockito/tests/Stress.java
@@ -0,0 +1,56 @@
+/*
+ * 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.assertEquals;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.util.Log;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class Stress {
+    private static final String LOG_TAG = Stress.class.getSimpleName();
+    private static final int NUM_TESTS = 80000;
+
+    public static class TestClass {
+        public String echo(String in) {
+            return in;
+        }
+    }
+
+    @Test
+    public void mockALot() {
+        for (int i = 0; i < NUM_TESTS; i++) {
+            if (i % 1024 == 0) {
+                Log.i(LOG_TAG, "Ran " + i + "/" + NUM_TESTS + " tests");
+            }
+
+            TestClass m = mock(TestClass.class);
+            when(m.echo(eq("marco!"))).thenReturn("polo");
+            assertEquals("polo", m.echo("marco!"));
+            verify(m).echo("marco!");
+        }
+    }
+}
diff --git a/dexmaker-mockito/build.gradle b/dexmaker-mockito/build.gradle
index 96479c5..46cdd8f 100644
--- a/dexmaker-mockito/build.gradle
+++ b/dexmaker-mockito/build.gradle
@@ -1,3 +1,15 @@
+buildscript {
+    repositories {
+        maven {
+            url "https://plugins.gradle.org/m2/"
+        }
+    }
+    dependencies {
+        classpath "net.ltgt.gradle:gradle-errorprone-plugin:0.0.13"
+    }
+}
+
+apply plugin: "net.ltgt.errorprone"
 apply plugin: 'java'
 apply from: "$rootDir/gradle/publishing.gradle"
 
@@ -12,7 +24,7 @@
 }
 
 dependencies {
-    compile project(":dexmaker")
+    implementation project(':dexmaker')
 
-    compile 'org.mockito:mockito-core:2.15.0', { exclude group: "net.bytebuddy" }
+    implementation 'org.mockito:mockito-core:2.21.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 19f371e..4cdaf44 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,9 +16,6 @@
 
 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;
@@ -39,12 +36,19 @@
  * 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();
+    private boolean isApi28;
 
-    public DexmakerMockMaker() {
-        if (Build.VERSION.SDK_INT >= 28) {
+    public DexmakerMockMaker() throws Exception {
+        try {
+            Class buildVersion = Class.forName("android.os.Build$VERSION");
+            isApi28 = buildVersion.getDeclaredField("SDK_INT").getInt(null) >= 28;
+        } catch (ClassNotFoundException e) {
+            System.err.println("Could not determine platform API level, assuming >= 28: " + e);
+            isApi28 = true;
+        }
+
+        if (isApi28) {
             // Blacklisted APIs were introduced in Android P:
             //
             // https://android-developers.googleblog.com/2018/02/
@@ -69,8 +73,8 @@
             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.");
+                System.err.println("Cannot allow LenientCopyTool to copy spies of blacklisted "
+                        + "fields. This might break spying on system classes.");
             }
         }
     }
@@ -95,15 +99,19 @@
         } else {
             // support concrete classes via dexmaker's ProxyBuilder
             try {
-                ProxyBuilder b = ProxyBuilder.forClass(typeToMock)
+                ProxyBuilder builder = ProxyBuilder.forClass(typeToMock)
                         .implementing(extraInterfaces);
 
+                if (isApi28) {
+                    builder.markTrusted();
+                }
+
                 if (Boolean.parseBoolean(
                         System.getProperty("dexmaker.share_classloader", "false"))) {
-                    b.withSharedClassLoader();
+                    builder.withSharedClassLoader();
                 }
 
-                Class<? extends T> proxyClass = b.buildProxyClass();
+                Class<? extends T> proxyClass = builder.buildProxyClass();
                 T mock = unsafeAllocator.newInstance(proxyClass);
                 ProxyBuilder.setInvocationHandler(mock, invocationHandler);
                 return mock;
diff --git a/dexmaker-tests/Android.mk b/dexmaker-tests/Android.mk
deleted file mode 100644
index 3ed4111..0000000
--- a/dexmaker-tests/Android.mk
+++ /dev/null
@@ -1,47 +0,0 @@
-# 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.
-#
-#
-
-LOCAL_PATH := $(call my-dir)
-
-# Build Dexmaker's tests
-#
-# Run the tests as follows:
-#   vogar --classpath ${ANDROID_PRODUCT_OUT}/obj/JAVA_LIBRARIES/dexmaker-tests_intermediates/javalib.jar \
-        com.android.dx
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := dexmaker-tests
-LOCAL_SDK_VERSION := 17
-LOCAL_SRC_FILES := $(call all-java-files-under, src/androidTest/java)
-LOCAL_STATIC_JAVA_LIBRARIES := dexmaker android-support-test
-LOCAL_ERROR_PRONE_FLAGS := -Xep:JUnit4TestNotRun:WARN
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
-# Build a test APK
-#
-# Run the tests as follows:
-# m -j32 DexmakerTests && \
-        am install -r -g $OUT/data/app/DexmakerTests/DexmakerTests.apk \
-        adb shell am instrument -w com.google.dexmaker.tests
-#
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := DexmakerTests
-LOCAL_SDK_VERSION := current
-LOCAL_STATIC_JAVA_LIBRARIES := \
-        dexmaker-tests
-
-include $(BUILD_PACKAGE)
diff --git a/dexmaker-tests/build.gradle b/dexmaker-tests/build.gradle
index 5bd40cd..5ae5f2d 100644
--- a/dexmaker-tests/build.gradle
+++ b/dexmaker-tests/build.gradle
@@ -1,27 +1,41 @@
-apply plugin: 'com.android.application'
-
-repositories {
-    jcenter()
-}
-
-android {
-    compileSdkVersion 25
-    buildToolsVersion '25.0.0'
-
-    defaultConfig {
-        applicationId "com.linkedin.dexmaker"
-        minSdkVersion 8
-        targetSdkVersion 25
-        versionCode 1
-        versionName VERSION_NAME
-
-        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+buildscript {
+    repositories {
+        maven {
+            url "https://plugins.gradle.org/m2/"
+        }
+    }
+    dependencies {
+        classpath "net.ltgt.gradle:gradle-errorprone-plugin:0.0.13"
     }
 }
 
-dependencies {
-    compile project(":dexmaker")
+apply plugin: "net.ltgt.errorprone"
+apply plugin: 'com.android.application'
 
-    androidTestCompile 'com.android.support.test:runner:0.5'
-    androidTestCompile 'junit:junit:4.12'
+android {
+    compileSdkVersion 28
+    buildToolsVersion '28.0.3'
+
+    defaultConfig {
+        applicationId 'com.linkedin.dexmaker'
+        minSdkVersion 8
+        targetSdkVersion 28
+        versionCode 1
+        versionName VERSION_NAME
+
+        testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
+    }
+}
+
+repositories {
+    jcenter()
+    google()
+}
+
+dependencies {
+    implementation project(":dexmaker")
+
+    //noinspection GradleDependency
+    androidTestImplementation 'com.android.support.test:runner:0.5'
+    androidTestImplementation 'junit:junit:4.12'
 }
diff --git a/dexmaker-tests/src/androidTest/java/com/android/dx/AnnotationIdTest.java b/dexmaker-tests/src/androidTest/java/com/android/dx/AnnotationIdTest.java
index 43731ee..ba1a259 100644
--- a/dexmaker-tests/src/androidTest/java/com/android/dx/AnnotationIdTest.java
+++ b/dexmaker-tests/src/androidTest/java/com/android/dx/AnnotationIdTest.java
@@ -15,11 +15,22 @@
  */
 package com.android.dx;
 
-import android.support.test.InstrumentationRegistry;
+import static com.android.dx.TypeId.*;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import android.os.Build;
+
+import androidx.test.InstrumentationRegistry;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
+import static java.lang.reflect.Modifier.PUBLIC;
+
 import java.io.File;
 import java.lang.annotation.*;
 import java.lang.reflect.Method;
@@ -27,11 +38,6 @@
 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 {
 
     /**
@@ -227,6 +233,8 @@
      */
     @Test
     public void addMethodAnnotationWithEnumElement() throws Exception {
+        assumeTrue(Build.VERSION.SDK_INT >= 21);
+
         MethodId<?, Void> methodId = generateVoidMethod(TypeId.get(Enum.class));
         AnnotationId.Element element = new AnnotationId.Element("elementEnum", ElementEnum.INSTANCE_1);
         addAnnotationToMethod(methodId, element);
@@ -259,6 +267,8 @@
      */
     @Test
     public void addMethodAnnotationWithMultiElements() throws Exception {
+        assumeTrue(Build.VERSION.SDK_INT >= 21);
+
         MethodId<?, Void> methodId = generateVoidMethod();
         AnnotationId.Element element1 = new AnnotationId.Element("elementClass", AnnotationId.class);
         AnnotationId.Element element2 = new AnnotationId.Element("elementEnum", ElementEnum.INSTANCE_1);
@@ -280,6 +290,8 @@
      */
     @Test
     public void addMethodAnnotationWithDuplicateElements() throws Exception {
+        assumeTrue(Build.VERSION.SDK_INT >= 21);
+
         MethodId<?, Void> methodId = generateVoidMethod();
         AnnotationId.Element element1 = new AnnotationId.Element("elementEnum", ElementEnum.INSTANCE_1);
         AnnotationId.Element element2 = new AnnotationId.Element("elementEnum", ElementEnum.INSTANCE_0);
diff --git a/dexmaker-tests/src/androidTest/java/com/android/dx/AppDataDirGuesserTest.java b/dexmaker-tests/src/androidTest/java/com/android/dx/AppDataDirGuesserTest.java
index a4a3407..b2128d7 100644
--- a/dexmaker-tests/src/androidTest/java/com/android/dx/AppDataDirGuesserTest.java
+++ b/dexmaker-tests/src/androidTest/java/com/android/dx/AppDataDirGuesserTest.java
@@ -146,6 +146,7 @@
         final Set<String> notWriteable = new HashSet<>();
         return new TestCondition() {
             private Integer processUid;
+            @Override
             public void shouldGive(String... files) {
                 AppDataDirGuesser guesser = new AppDataDirGuesser() {
                     @Override
@@ -169,6 +170,7 @@
                 }
             }
 
+            @Override
             public TestCondition withNonWriteable(String... files) {
                 notWriteable.addAll(Arrays.asList(files));
                 return this;
diff --git a/dexmaker-tests/src/androidTest/java/com/android/dx/DexMakerTest.java b/dexmaker-tests/src/androidTest/java/com/android/dx/DexMakerTest.java
index f2b457f..af520e1 100644
--- a/dexmaker-tests/src/androidTest/java/com/android/dx/DexMakerTest.java
+++ b/dexmaker-tests/src/androidTest/java/com/android/dx/DexMakerTest.java
@@ -16,10 +16,36 @@
 
 package com.android.dx;
 
-import android.support.test.InstrumentationRegistry;
+import static com.android.dx.util.TestUtil.DELTA_DOUBLE;
+import static com.android.dx.util.TestUtil.DELTA_FLOAT;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import android.os.Build;
+
+import androidx.test.InstrumentationRegistry;
+
+import dalvik.system.BaseDexClassLoader;
+
 import org.junit.Before;
 import org.junit.Test;
 
+import static java.lang.reflect.Modifier.ABSTRACT;
+import static java.lang.reflect.Modifier.FINAL;
+import static java.lang.reflect.Modifier.NATIVE;
+import static java.lang.reflect.Modifier.PRIVATE;
+import static java.lang.reflect.Modifier.PROTECTED;
+import static java.lang.reflect.Modifier.PUBLIC;
+import static java.lang.reflect.Modifier.STATIC;
+import static java.lang.reflect.Modifier.SYNCHRONIZED;
+
 import java.io.File;
 import java.io.FilenameFilter;
 import java.io.IOException;
@@ -31,21 +57,6 @@
 import java.util.List;
 import java.util.concurrent.Callable;
 
-import static com.android.dx.util.TestUtil.DELTA_DOUBLE;
-import static com.android.dx.util.TestUtil.DELTA_FLOAT;
-import static java.lang.reflect.Modifier.ABSTRACT;
-import static java.lang.reflect.Modifier.FINAL;
-import static java.lang.reflect.Modifier.NATIVE;
-import static java.lang.reflect.Modifier.PRIVATE;
-import static java.lang.reflect.Modifier.PROTECTED;
-import static java.lang.reflect.Modifier.PUBLIC;
-import static java.lang.reflect.Modifier.STATIC;
-import static java.lang.reflect.Modifier.SYNCHRONIZED;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
 /**
  * This generates a class named 'Generated' with one or more generated methods
  * and fields. In loads the generated class into the current VM and uses
@@ -135,7 +146,7 @@
         addDefaultConstructor();
 
         Class<?> generatedClass = generateAndLoad();
-        Object instance = generatedClass.newInstance();
+        Object instance = generatedClass.getDeclaredConstructor().newInstance();
         Method method = generatedClass.getMethod("call");
         method.invoke(instance);
     }
@@ -177,7 +188,7 @@
         addDefaultConstructor();
 
         Class<?> generatedClass = generateAndLoad();
-        Object instance = generatedClass.newInstance();
+        Object instance = generatedClass.getDeclaredConstructor().newInstance();
         Method method = generatedClass.getMethod("call", int.class);
         method.invoke(instance, 0);
     }
@@ -244,7 +255,7 @@
         addDefaultConstructor();
 
         Class<?> generatedClass = generateAndLoad();
-        Object instance = generatedClass.newInstance();
+        Object instance = generatedClass.getDeclaredConstructor().newInstance();
         Method method = generatedClass.getMethod("call", generatedClass);
         assertEquals(5, method.invoke(null, instance));
     }
@@ -278,7 +289,7 @@
         addDefaultConstructor();
 
         Class<?> generatedClass = generateAndLoad();
-        Object instance = generatedClass.newInstance();
+        Object instance = generatedClass.getDeclaredConstructor().newInstance();
         Method method = generatedClass.getMethod("superHashCode");
         assertEquals(System.identityHashCode(instance), method.invoke(instance));
     }
@@ -299,6 +310,7 @@
         code.returnValue(localResult);
 
         Callable<Object> callable = new Callable<Object>() {
+            @Override
             public Object call() throws Exception {
                 return "abc";
             }
@@ -425,7 +437,7 @@
         addDefaultConstructor();
 
         Class<?> generatedClass = generateAndLoad();
-        Object instance = generatedClass.newInstance();
+        Object instance = generatedClass.getDeclaredConstructor().newInstance();
 
         Field a = generatedClass.getField("a");
         assertEquals(int.class, a.getType());
@@ -691,6 +703,7 @@
     }
 
     @Test
+    @SuppressWarnings("FloatingPointLiteralPrecision")
     public void testCastFloatingPointToInteger() throws Exception {
         Method floatToInt = numericCastingMethod(float.class, int.class);
         assertEquals(0, floatToInt.invoke(null, 0.0f));
@@ -1046,7 +1059,7 @@
         assertEquals((short) 0x1234, instance.shortValue);
     }
 
-    public class Instance {
+    public static class Instance {
         public int intValue;
         public long longValue;
         public float floatValue;
@@ -1871,7 +1884,7 @@
         addDefaultConstructor();
 
         Class<?> generatedClass = generateAndLoad();
-        Object instance = generatedClass.newInstance();
+        Object instance = generatedClass.getDeclaredConstructor().newInstance();
         Method method = generatedClass.getMethod("call");
         assertTrue(Modifier.isSynchronized(method.getModifiers()));
         try {
@@ -1905,7 +1918,7 @@
         addDefaultConstructor();
 
         Class<?> generatedClass = generateAndLoad();
-        Object instance = generatedClass.newInstance();
+        Object instance = generatedClass.getDeclaredConstructor().newInstance();
         Method method = generatedClass.getMethod("call");
         assertFalse(Modifier.isSynchronized(method.getModifiers()));
         method.invoke(instance); // will take 100ms
@@ -2121,7 +2134,7 @@
         TypeId<IllegalStateException> iseType = TypeId.get(IllegalStateException.class);
         Local<IllegalStateException> localIse = code.newLocal(iseType);
         if (params.length > 0) {
-            if (params[0] == typeId) {
+            if (params[0].equals(typeId)) {
                 Local<?> localResult = code.getParameter(0, TypeId.INT);
                 code.returnValue(localResult);
             } else {
@@ -2132,6 +2145,30 @@
         }
     }
 
+    public interface BlankInterfaceA {}
+
+    public interface BlankInterfaceB {}
+
+    @Test
+    public void testCaching_DifferentInterfaces() throws Exception {
+        int origSize = getDataDirectory().listFiles().length;
+
+        // Create new dexmaker generator with BlankInterfaceA.
+        dexMaker = new DexMaker();
+        TypeId interfaceA = TypeId.get(BlankInterfaceA.class);
+        dexMaker.declare(GENERATED, "Generated.java", PUBLIC, TypeId.OBJECT, interfaceA);
+        generateAndLoad();
+        int numFiles = getDataDirectory().listFiles().length;
+        assertTrue(origSize < numFiles);
+
+        // Create new dexmaker generator with BlankInterfaceB.
+        dexMaker = new DexMaker();
+        TypeId interfaceB = TypeId.get(BlankInterfaceB.class);
+        dexMaker.declare(GENERATED, "Generated.java", PUBLIC, TypeId.OBJECT, interfaceB);
+        generateAndLoad();
+        assertTrue(numFiles < getDataDirectory().listFiles().length);
+    }
+
     @Test
     public void testCaching_Constructors() throws Exception {
         int origSize = getDataDirectory().listFiles().length;
@@ -2188,6 +2225,7 @@
 
     private File[] getJarFiles() {
         return getDataDirectory().listFiles(new FilenameFilter() {
+            @Override
             public boolean accept(File dir, String name) {
                 return name.endsWith(".jar");
             }
@@ -2216,4 +2254,64 @@
         return dexMaker.generateAndLoad(getClass().getClassLoader(), getDataDirectory())
                 .loadClass("Generated");
     }
+
+    private final ClassLoader commonClassLoader = new BaseDexClassLoader(
+            getDataDirectory().getPath(), getDataDirectory(), getDataDirectory().getPath(),
+            DexMakerTest.class.getClassLoader());
+
+    private final ClassLoader uncommonClassLoader = new ClassLoader() {
+        @Override
+        public Class<?> loadClass(String name) throws ClassNotFoundException {
+            throw new IllegalStateException("Not used");
+        }
+    };
+
+    private static void loadWithSharedClassLoader(ClassLoader cl, boolean markAsTrusted,
+                                                  boolean shouldUseCL) throws Exception {
+        DexMaker d = new DexMaker();
+        d.setSharedClassLoader(cl);
+
+        if (markAsTrusted) {
+            d.markAsTrusted();
+        }
+
+        ClassLoader selectedCL = d.generateAndLoad(null, getDataDirectory());
+
+        if (shouldUseCL) {
+            assertSame(cl, selectedCL);
+        } else {
+            assertNotSame(cl, selectedCL);
+
+            // An appropriate fallback should have been selected
+            assertNotNull(selectedCL);
+        }
+    }
+
+    @Test
+    public void loadWithUncommonSharedClassLoader() throws Exception{
+        assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N);
+
+        loadWithSharedClassLoader(uncommonClassLoader, false, false);
+    }
+
+    @Test
+    public void loadWithCommonSharedClassLoader() throws Exception{
+        assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N);
+
+        loadWithSharedClassLoader(commonClassLoader, false, true);
+    }
+
+    @Test
+    public void loadAsTrustedWithUncommonSharedClassLoader() throws Exception{
+        assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P);
+
+        loadWithSharedClassLoader(uncommonClassLoader, true, false);
+    }
+
+    @Test
+    public void loadAsTrustedWithCommonSharedClassLoader() throws Exception{
+        assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P);
+
+        loadWithSharedClassLoader(commonClassLoader, true, true);
+    }
 }
diff --git a/dexmaker-tests/src/androidTest/java/com/android/dx/stock/ProxyBuilderTest.java b/dexmaker-tests/src/androidTest/java/com/android/dx/stock/ProxyBuilderTest.java
index 7127ec5..19b5839 100644
--- a/dexmaker-tests/src/androidTest/java/com/android/dx/stock/ProxyBuilderTest.java
+++ b/dexmaker-tests/src/androidTest/java/com/android/dx/stock/ProxyBuilderTest.java
@@ -16,6 +16,8 @@
 
 package com.android.dx.stock;
 
+import android.os.Build;
+
 import com.android.dx.DexMakerTest;
 import junit.framework.AssertionFailedError;
 import org.junit.After;
@@ -44,6 +46,8 @@
 import static junit.framework.Assert.assertTrue;
 import static junit.framework.Assert.fail;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assume.assumeTrue;
 
 public class ProxyBuilderTest {
     private FakeInvocationHandler fakeHandler = new FakeInvocationHandler();
@@ -202,6 +206,7 @@
         }
     }
 
+    @Test
     public void testProxyingPackagePrivateMethods_NotIntercepted()
             throws Throwable {
         HasPackagePrivateMethod proxy = proxyFor(HasPackagePrivateMethod.class)
@@ -215,12 +220,28 @@
 
         try {
             proxy.result();
-            fail();
         } catch (AssertionFailedError expected) {
+            return;
+        }
+        fail();
+    }
 
+    public static class HasPackagePrivateMethodSharedClassLoader {
+        String result() {
+            throw new AssertionFailedError();
         }
     }
 
+    @Test
+    public void testProxyingPackagePrivateMethodsWithSharedClassLoader_AreIntercepted()
+            throws Throwable {
+        assumeTrue(Build.VERSION.SDK_INT >= 24);
+
+        assertEquals("fake result", proxyFor(HasPackagePrivateMethodSharedClassLoader.class)
+                .withSharedClassLoader().build().result());
+    }
+
+
     public static class HasProtectedMethod {
         protected String result() {
             throw new AssertionFailedError();
@@ -271,7 +292,7 @@
     }
 
     @Test
-    @SuppressWarnings("EqualsWithItself")
+    @SuppressWarnings({"EqualsWithItself", "SelfEquals"})
     public void testObjectMethodsAreAlsoProxied() throws Throwable {
         Object proxy = proxyFor(Object.class).build();
         fakeHandler.setFakeResult("mystring");
@@ -335,6 +356,7 @@
     }
 
     public static class InvokeSuperHandler implements InvocationHandler {
+        @Override
         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
             return ProxyBuilder.callSuper(proxy, method, args);
         }
@@ -405,6 +427,7 @@
     @Test
     public void testSinglePrimitiveParameter() throws Throwable {
         InvocationHandler handler = new InvocationHandler() {
+            @Override
             public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                 return "asdf" + ((Integer) args[0]).intValue();
             }
@@ -486,6 +509,7 @@
     @Test
     public void testSometimesDelegateToSuper() throws Exception {
         InvocationHandler delegatesOddValues = new InvocationHandler() {
+            @Override
             public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                 if (method.getName().equals("method")) {
                     int intValue = ((Integer) args[0]).intValue();
@@ -508,6 +532,7 @@
     @Test
     public void testCallSuperThrows() throws Exception {
         InvocationHandler handler = new InvocationHandler() {
+            @Override
             public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                 return ProxyBuilder.callSuper(o, method, objects);
             }
@@ -582,6 +607,21 @@
         assertEquals("fake result", proxyFor(AbstractClass.class).build().getValue());
     }
 
+    @Test
+    public void testCallAbstractSuperMethod() throws Exception {
+        AbstractClass a = proxyFor(AbstractClass.class).build();
+
+        // Setting the handler to null routes all calls to the real methods. In this case the real
+        // method is abstract and cannot be called
+        ProxyBuilder.setInvocationHandler(a, null);
+
+        try {
+            a.getValue();
+            fail();
+        } catch (AbstractMethodError expected) {
+        }
+    }
+
     public static class CtorHasDeclaredException {
         public CtorHasDeclaredException() throws IOException {
             throw new IOException();
@@ -616,10 +656,11 @@
         }
         try {
             proxyFor(CtorHasError.class).build();
-            fail();
         } catch (Error expected) {
             assertEquals("my message again", expected.getMessage());
+            return;
         }
+        fail();
     }
 
     @Test
@@ -771,6 +812,7 @@
     @Test
     public void testImplementInterfaceCallingThroughConcreteClass() throws Throwable {
         InvocationHandler invocationHandler = new InvocationHandler() {
+            @Override
             public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                 assertEquals("a", ProxyBuilder.callSuper(o, method, objects));
                 return "b";
@@ -796,6 +838,7 @@
         final AtomicInteger count = new AtomicInteger();
 
         InvocationHandler invocationHandler = new InvocationHandler() {
+            @Override
             public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                 count.incrementAndGet();
                 return ProxyBuilder.callSuper(o, method, objects);
@@ -817,6 +860,7 @@
     }
 
     public static class ImplementsCallable implements Callable<String> {
+        @Override
         public String call() throws Exception {
             return "a";
         }
@@ -831,6 +875,7 @@
     @Test
     public void testInterfacesSameNamesDifferentReturnTypes() throws Throwable {
         InvocationHandler handler = new InvocationHandler() {
+            @Override
             public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                 if (method.getReturnType() == void.class) {
                     return null;
@@ -859,6 +904,23 @@
         assertEquals(3, c.foo());
     }
 
+
+    @Test
+    public void testCallInterfaceSuperMethod() throws Exception {
+        FooReturnsVoid f = (FooReturnsVoid)proxyFor(Object.class).implementing(FooReturnsVoid.class)
+                .build();
+
+        // Setting the handler to null routes all calls to the real methods. In this case the real
+        // method is a method of an interface and cannot be called
+        ProxyBuilder.setInvocationHandler(f, null);
+
+        try {
+            f.foo();
+            fail();
+        } catch (AbstractMethodError expected) {
+        }
+    }
+
     @Test
     public void testInterfacesSameNamesSameReturnType() throws Throwable {
         Object o = proxyFor(Object.class)
@@ -974,6 +1036,7 @@
     private static class FakeInvocationHandler implements InvocationHandler {
         private Object fakeResult = "fake result";
 
+        @Override
         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
             return fakeResult;
         }
@@ -1077,6 +1140,7 @@
     }
 
     public static class ConcreteClassA implements FooReturnsInt {
+        @Override
         // from FooReturnsInt
         public int foo() {
             return 1;
@@ -1089,6 +1153,7 @@
     }
 
     public static class ConcreteClassB implements FooReturnsInt {
+        @Override
         // from FooReturnsInt
         public int foo() {
             return 0;
@@ -1150,6 +1215,7 @@
         }
 
         InvocationHandler handler = new InvokeSuperHandler() {
+            @Override
             public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                 if (method.getName().equals("returnA")) {
                     return "fake A";
@@ -1174,4 +1240,26 @@
         assertEquals("fake E", proxy.returnE());
 
     }
+
+    @Test
+    public void testImplementingDifferentInterfacesWithSharedClassLoader() throws IOException {
+        assumeTrue(Build.VERSION.SDK_INT >= 24);
+
+        Class<?> c1 = ProxyBuilder.forClass(SimpleClass.class)
+                .implementing(Runnable.class).withSharedClassLoader().buildProxyClass();
+        Class<?> c2 = ProxyBuilder.forClass(SimpleClass.class)
+                .implementing(Callable.class).withSharedClassLoader().buildProxyClass();
+        assertNotSame(c1, c2);
+    }
+
+    @Test
+    public void testInterfaceOrder() throws Exception {
+        Class<?> c1 = proxyFor(SimpleClass.class)
+                .implementing(Runnable.class, Callable.class).buildProxyClass();
+        assertEquals(new Class[]{Runnable.class, Callable.class}, c1.getInterfaces());
+
+        Class<?> c2 = proxyFor(SimpleClass.class)
+                .implementing(Callable.class, Runnable.class).buildProxyClass();
+        assertEquals(new Class[]{Callable.class, Runnable.class}, c2.getInterfaces());
+    }
 }
diff --git a/dexmaker-tests/AndroidManifest.xml b/dexmaker-tests/src/main/AndroidManifest.xml
similarity index 94%
rename from dexmaker-tests/AndroidManifest.xml
rename to dexmaker-tests/src/main/AndroidManifest.xml
index 7849b0b..62cf305 100644
--- a/dexmaker-tests/AndroidManifest.xml
+++ b/dexmaker-tests/src/main/AndroidManifest.xml
@@ -19,5 +19,5 @@
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
 
-    <application/>
+    <application android:allowBackup="false" />
 </manifest>
\ No newline at end of file
diff --git a/dexmaker/build.gradle b/dexmaker/build.gradle
index 73c9344..7e8762f 100644
--- a/dexmaker/build.gradle
+++ b/dexmaker/build.gradle
@@ -1,3 +1,15 @@
+buildscript {
+    repositories {
+        maven {
+            url "https://plugins.gradle.org/m2/"
+        }
+    }
+    dependencies {
+        classpath "net.ltgt.gradle:gradle-errorprone-plugin:0.0.13"
+    }
+}
+
+apply plugin: "net.ltgt.errorprone"
 apply plugin: 'java'
 apply from: "$rootDir/gradle/publishing.gradle"
 
@@ -11,6 +23,10 @@
     jcenter()
 }
 
+tasks.withType(JavaCompile) {
+    options.compilerArgs += ["-Xep:StringSplitter:OFF"]
+}
+
 dependencies {
-    compile 'com.jakewharton.android.repackaged:dalvik-dx:7.1.0_r7'
+    implementation 'com.jakewharton.android.repackaged:dalvik-dx:9.0.0_r3'
 }
diff --git a/dexmaker/src/main/java/com/android/dx/AnnotationId.java b/dexmaker/src/main/java/com/android/dx/AnnotationId.java
index bcc201b..366c904 100644
--- a/dexmaker/src/main/java/com/android/dx/AnnotationId.java
+++ b/dexmaker/src/main/java/com/android/dx/AnnotationId.java
@@ -130,7 +130,7 @@
             throw new IllegalStateException("This annotation is not for method");
         }
 
-        if (method.declaringType != declaringType) {
+        if (!method.declaringType.equals(declaringType)) {
             throw new IllegalArgumentException("Method" + method + "'s declaring type is inconsistent with" + this);
         }
 
diff --git a/dexmaker/src/main/java/com/android/dx/DexMaker.java b/dexmaker/src/main/java/com/android/dx/DexMaker.java
index 755c9fa..e45ead0 100644
--- a/dexmaker/src/main/java/com/android/dx/DexMaker.java
+++ b/dexmaker/src/main/java/com/android/dx/DexMaker.java
@@ -49,8 +49,6 @@
 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,
@@ -198,9 +196,13 @@
  * }</pre>
  */
 public final class DexMaker {
-    private static final String LOG_TAG = DexMaker.class.getSimpleName();
-
     private final Map<TypeId<?>, TypeDeclaration> types = new LinkedHashMap<>();
+
+    // Only warn about not being able to deal with blacklisted methods once. Often this is no
+    // problem and warning on every class load is too spammy.
+    private static boolean didWarnBlacklistedMethods;
+    private static boolean didWarnNonBaseDexClassLoader;
+
     private ClassLoader sharedClassLoader;
     private DexFile outputDex;
     private boolean markAsTrusted;
@@ -230,7 +232,8 @@
     public void declare(TypeId<?> type, String sourceFile, int flags,
             TypeId<?> supertype, TypeId<?>... interfaces) {
         TypeDeclaration declaration = getTypeDeclaration(type);
-        int supportedFlags = Modifier.PUBLIC | Modifier.FINAL | Modifier.ABSTRACT;
+        int supportedFlags = Modifier.PUBLIC | Modifier.FINAL | Modifier.ABSTRACT
+                | AccessFlags.ACC_SYNTHETIC;
         if ((flags & ~supportedFlags) != 0) {
             throw new IllegalArgumentException("Unexpected flag: "
                     + Integer.toHexString(flags));
@@ -263,7 +266,8 @@
         }
 
         int supportedFlags = Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED
-                | Modifier.STATIC | Modifier.FINAL | Modifier.SYNCHRONIZED;
+                | Modifier.STATIC | Modifier.FINAL | Modifier.SYNCHRONIZED
+                | AccessFlags.ACC_SYNTHETIC | AccessFlags.ACC_BRIDGE;
         if ((flags & ~supportedFlags) != 0) {
             throw new IllegalArgumentException("Unexpected flag: "
                     + Integer.toHexString(flags));
@@ -301,7 +305,8 @@
         }
 
         int supportedFlags = Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED
-                | Modifier.STATIC | Modifier.FINAL | Modifier.VOLATILE | Modifier.TRANSIENT;
+                | Modifier.STATIC | Modifier.FINAL | Modifier.VOLATILE | Modifier.TRANSIENT
+                | AccessFlags.ACC_SYNTHETIC;
         if ((flags & ~supportedFlags) != 0) {
             throw new IllegalArgumentException("Unexpected flag: "
                     + Integer.toHexString(flags));
@@ -321,7 +326,7 @@
     public byte[] generate() {
         if (outputDex == null) {
             DexOptions options = new DexOptions();
-            options.targetApiLevel = DexFormat.API_NO_EXTENDED_OPCODES;
+            options.minSdkVersion = DexFormat.API_NO_EXTENDED_OPCODES;
             outputDex = new DexFile(options);
         }
 
@@ -351,7 +356,8 @@
             TypeDeclaration decl = getTypeDeclaration(typeId);
             Set<MethodId> methodSet = decl.methods.keySet();
             if (decl.supertype != null) {
-                checksums[i++] = 31 * decl.supertype.hashCode() + methodSet.hashCode();
+                int sum = 31 * decl.supertype.hashCode() + decl.interfaces.hashCode();
+                checksums[i++] = 31 * sum + methodSet.hashCode();
             }
         }
         Arrays.sort(checksums);
@@ -371,6 +377,9 @@
      * class loader. One common case for this requirement is a mock class wanting to mock package
      * private methods of the original class.
      *
+     * <p>If the classLoader is not a subclass of {@code dalvik.system.BaseDexClassLoader} this
+     * option is ignored.
+     *
      * @param classLoader the class loader the new class should be loaded by
      */
     public void setSharedClassLoader(ClassLoader classLoader) {
@@ -383,41 +392,71 @@
 
     private ClassLoader generateClassLoader(File result, File dexCache, ClassLoader parent) {
         try {
+            boolean shareClassLoader = sharedClassLoader != null;
+
+            ClassLoader preferredClassLoader = null;
+            if (parent != null) {
+                preferredClassLoader = parent;
+            } else if (sharedClassLoader != null) {
+                preferredClassLoader = sharedClassLoader;
+            }
+
+            Class baseDexClassLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader");
+
+            if (shareClassLoader) {
+                if (!baseDexClassLoaderClass.isAssignableFrom(preferredClassLoader.getClass())) {
+                    if (!preferredClassLoader.getClass().getName().equals(
+                            "java.lang.BootClassLoader")) {
+                        if (!didWarnNonBaseDexClassLoader) {
+                            System.err.println("Cannot share classloader as shared classloader '"
+                                    + preferredClassLoader + "' is not a subclass of '"
+                                    + baseDexClassLoaderClass
+                                    + "'");
+                            didWarnNonBaseDexClassLoader = true;
+                        }
+                    }
+
+                    shareClassLoader = false;
+                }
+            }
+
             // 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;
+                    if (shareClassLoader) {
+                        preferredClassLoader.getClass().getMethod("addDexPath", String.class,
+                                Boolean.TYPE).invoke(preferredClassLoader, result.getPath(), true);
+                        return preferredClassLoader;
                     } else {
-                        return (ClassLoader) Class.forName("dalvik.system.BaseDexClassLoader")
+                        return (ClassLoader) baseDexClassLoaderClass
                                 .getConstructor(String.class, File.class, String.class,
                                         ClassLoader.class, Boolean.TYPE)
                                 .newInstance(result.getPath(), dexCache.getAbsoluteFile(), null,
-                                        parent, true);
+                                        preferredClassLoader, 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());
+                        if (!didWarnBlacklistedMethods) {
+                            System.err.println("Cannot allow to call blacklisted super methods. "
+                                    + "This might break spying on system classes." + e.getCause());
+                            didWarnBlacklistedMethods = true;
+                        }
                     } else {
                         throw e;
                     }
                 }
             }
 
-            if (sharedClassLoader != null) {
-                ClassLoader loader = parent != null ? parent : sharedClassLoader;
-                loader.getClass().getMethod("addDexPath", String.class).invoke(loader,
-                        result.getPath());
-                return loader;
+            if (shareClassLoader) {
+                preferredClassLoader.getClass().getMethod("addDexPath", String.class).invoke(
+                        preferredClassLoader, result.getPath());
+                return preferredClassLoader;
             } else {
                 return (ClassLoader) Class.forName("dalvik.system.DexClassLoader")
                         .getConstructor(String.class, String.class, String.class, ClassLoader.class)
-                        .newInstance(result.getPath(), dexCache.getAbsolutePath(), null, parent);
+                        .newInstance(result.getPath(), dexCache.getAbsolutePath(), null,
+                                preferredClassLoader);
             }
         } catch (ClassNotFoundException e) {
             throw new UnsupportedOperationException("load() requires a Dalvik VM", e);
@@ -452,7 +491,8 @@
      * property.
      *
      * @param parent the parent ClassLoader to be used when loading our
-     *     generated types
+     *     generated types (if set, overrides
+     *     {@link #setSharedClassLoader(ClassLoader) shared class loader}.
      * @param dexCache the destination directory where generated and optimized
      *     dex files will be written. If null, this class will try to guess the
      *     application's private data dir.
@@ -501,7 +541,7 @@
     DexFile getDexFile() {
         if (outputDex == null) {
             DexOptions options = new DexOptions();
-            options.targetApiLevel = DexFormat.API_NO_EXTENDED_OPCODES;
+            options.minSdkVersion = DexFormat.API_NO_EXTENDED_OPCODES;
             outputDex = new DexFile(options);
         }
         return outputDex;
@@ -532,7 +572,7 @@
             }
 
             DexOptions dexOptions = new DexOptions();
-            dexOptions.targetApiLevel = DexFormat.API_NO_EXTENDED_OPCODES;
+            dexOptions.minSdkVersion = DexFormat.API_NO_EXTENDED_OPCODES;
 
             CstType thisType = type.constant;
 
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 053fb16..007abc1 100644
--- a/dexmaker/src/main/java/com/android/dx/stock/ProxyBuilder.java
+++ b/dexmaker/src/main/java/com/android/dx/stock/ProxyBuilder.java
@@ -34,21 +34,21 @@
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.lang.reflect.UndeclaredThrowableException;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.CopyOnWriteArraySet;
 
+import static java.lang.reflect.Modifier.ABSTRACT;
 import static java.lang.reflect.Modifier.PRIVATE;
 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>
@@ -132,8 +132,8 @@
      * Android's runtime doesn't support class unloading so there's little
      * value in using weak references.
      */
-    private static final Map<Class<?>, Class<?>> generatedProxyClasses
-            = Collections.synchronizedMap(new HashMap<Class<?>, Class<?>>());
+    private static final Map<ProxiedClass<?>, Class<?>> generatedProxyClasses
+            = Collections.synchronizedMap(new HashMap<ProxiedClass<?>, Class<?>>());
 
     private final Class<T> baseClass;
     private ClassLoader parentClassLoader = ProxyBuilder.class.getClassLoader();
@@ -141,9 +141,10 @@
     private File dexCache;
     private Class<?>[] constructorArgTypes = new Class[0];
     private Object[] constructorArgValues = new Object[0];
-    private Set<Class<?>> interfaces = new HashSet<>();
+    private List<Class<?>> interfaces = new ArrayList<>();
     private Method[] methods;
     private boolean sharedClassLoader;
+    private boolean markTrusted;
 
     private ProxyBuilder(Class<T> clazz) {
         baseClass = clazz;
@@ -180,11 +181,14 @@
     }
 
     public ProxyBuilder<T> implementing(Class<?>... interfaces) {
+        List<Class<?>> list = this.interfaces;
         for (Class<?> i : interfaces) {
             if (!i.isInterface()) {
                 throw new IllegalArgumentException("Not an interface: " + i.getName());
             }
-            this.interfaces.add(i);
+            if (!list.contains(i)) {
+                list.add(i);
+            }
         }
         return this;
     }
@@ -209,6 +213,11 @@
         return this;
     }
 
+    public ProxyBuilder<T> markTrusted() {
+        this.markTrusted = true;
+        return this;
+    }
+
     /**
      * Create a new instance of the class to proxy.
      *
@@ -258,27 +267,26 @@
      * {@link #setInvocationHandler(Object, InvocationHandler)}.
      */
     public Class<? extends T> buildProxyClass() throws IOException {
+        ClassLoader requestedClassloader;
+        if (sharedClassLoader) {
+            requestedClassloader = baseClass.getClassLoader();
+        } else {
+            requestedClassloader = parentClassLoader;
+        }
+
         // try the cache to see if we've generated this one before
         // we only populate the map with matching types
+        ProxiedClass<T> cacheKey =
+                new ProxiedClass<>(baseClass, interfaces, requestedClassloader, sharedClassLoader);
         @SuppressWarnings("unchecked")
-        Class<? extends T> proxyClass = (Class) generatedProxyClasses.get(baseClass);
+        Class<? extends T> proxyClass = (Class) generatedProxyClasses.get(cacheKey);
         if (proxyClass != null) {
-            boolean validClassLoader;
-            if (sharedClassLoader) {
-                ClassLoader parent = parentClassLoader != null ? parentClassLoader : baseClass
-                        .getClassLoader();
-                validClassLoader = proxyClass.getClassLoader() == parent;
-            } else {
-                validClassLoader = proxyClass.getClassLoader().getParent() == parentClassLoader;
-            }
-            if (validClassLoader && interfaces.equals(asSet(proxyClass.getInterfaces()))) {
-                return proxyClass; // cache hit!
-            }
+            return proxyClass; // cache hit!
         }
 
         // the cache missed; generate the class
         DexMaker dexMaker = new DexMaker();
-        String generatedName = getMethodNameForProxyOf(baseClass);
+        String generatedName = getMethodNameForProxyOf(baseClass, interfaces);
         TypeId<? extends T> generatedType = TypeId.get("L" + generatedName + ";");
         TypeId<T> superType = TypeId.get(baseClass);
         generateConstructorsAndFields(dexMaker, generatedType, superType, baseClass);
@@ -291,19 +299,26 @@
         }
 
         // Sort the results array so that they are in a deterministic fashion.
+        //
+        // We use the same parameters to sort as used in {@link MethodId#hashCode}. This is needed
+        // as e.g. making a method "public" instead of "protected" should not change the id's of the
+        // methods. If the id's would change the classes loaded from the cache would be incorrect.
         Arrays.sort(methodsToProxy, new Comparator<Method>() {
             @Override
             public int compare(Method method1, Method method2) {
-                return method1.toString().compareTo(method2.toString());
+                String m1Signature = method1.getDeclaringClass() + method1.getName() + Arrays.toString(method1.getParameterTypes()) + method1.getReturnType();
+                String m2Signature = method2.getDeclaringClass() + method2.getName() + Arrays.toString(method2.getParameterTypes()) + method2.getReturnType();
+
+                return m1Signature.compareTo(m2Signature);
             }
         });
 
         generateCodeForAllMethods(dexMaker, generatedType, methodsToProxy, superType);
         dexMaker.declare(generatedType, generatedName + ".generated", PUBLIC, superType, getInterfacesAsTypeIds());
         if (sharedClassLoader) {
-            dexMaker.setSharedClassLoader(baseClass.getClassLoader());
+            dexMaker.setSharedClassLoader(requestedClassloader);
         }
-        if (Build.VERSION.SDK_INT >= 28) {
+        if (markTrusted) {
             // The proxied class might have blacklisted methods. Blacklisting methods (and fields)
             // is a new feature of Android P:
             //
@@ -315,7 +330,12 @@
             // all generated classes as trusted.
             dexMaker.markAsTrusted();
         }
-        ClassLoader classLoader = dexMaker.generateAndLoad(parentClassLoader, dexCache);
+        ClassLoader classLoader;
+        if (sharedClassLoader) {
+            classLoader = dexMaker.generateAndLoad(null, dexCache);
+        } else {
+            classLoader = dexMaker.generateAndLoad(parentClassLoader, dexCache);
+        }
         try {
             proxyClass = loadClass(classLoader, generatedName);
         } catch (IllegalAccessError e) {
@@ -327,7 +347,7 @@
             throw new AssertionError(e);
         }
         setMethodsStaticField(proxyClass, methodsToProxy);
-        generatedProxyClasses.put(baseClass, proxyClass);
+        generatedProxyClasses.put(cacheKey, proxyClass);
         return proxyClass;
     }
 
@@ -424,6 +444,36 @@
         }
     }
 
+    /**
+     * Add
+     *
+     * <pre>
+     *     abstractMethodErrorMessage = method + " cannot be called";
+     *     abstractMethodError = new AbstractMethodError(abstractMethodErrorMessage);
+     *     throw abstractMethodError;
+     * </pre>
+     *
+     * to the {@code code}.
+     *
+     * @param code The code to add to
+     * @param method The method that is abstract
+     * @param abstractMethodErrorMessage The {@link Local} to store the error message
+     * @param abstractMethodError The {@link Local} to store the error object
+     */
+    private static void throwAbstractMethodError(Code code, Method method,
+                                                 Local<String> abstractMethodErrorMessage,
+                                                 Local<AbstractMethodError> abstractMethodError) {
+        TypeId<AbstractMethodError> abstractMethodErrorClass = TypeId.get(AbstractMethodError.class);
+
+        MethodId<AbstractMethodError, Void> abstractMethodErrorConstructor =
+                abstractMethodErrorClass.getConstructor(TypeId.STRING);
+        code.loadConstant(abstractMethodErrorMessage, "'" + method + "' cannot be called");
+        code.newInstance(abstractMethodError, abstractMethodErrorConstructor,
+                abstractMethodErrorMessage);
+
+        code.throwValue(abstractMethodError);
+    }
+
     private static <T, G extends T> void generateCodeForAllMethods(DexMaker dexMaker,
             TypeId<G> generatedType, Method[] methodsToProxy, TypeId<T> superclassType) {
         TypeId<InvocationHandler> handlerType = TypeId.get(InvocationHandler.class);
@@ -445,33 +495,10 @@
              *         ...
              *     }
              *
-             * Then the following code will generate a method on the proxy that looks something
-             * like this:
+             * Then the following dex byte code will generate a method on the proxy that looks
+             * something like this (in idiomatic Java):
              *
-             *     public int doSomething(Bar param0, int param1) {
-             *         int methodIndex = 4;
-             *         Method[] allMethods = Example_Proxy.$__methodArray;
-             *         Method thisMethod = allMethods[methodIndex];
-             *         int argsLength = 2;
-             *         Object[] args = new Object[argsLength];
-             *         InvocationHandler localHandler = this.$__handler;
-             *         // for-loop begins
-             *         int p = 0;
-             *         Bar parameter0 = param0;
-             *         args[p] = parameter0;
-             *         p = 1;
-             *         int parameter1 = param1;
-             *         Integer boxed1 = Integer.valueOf(parameter1);
-             *         args[p] = boxed1;
-             *         // for-loop ends
-             *         Object result = localHandler.invoke(this, thisMethod, args);
-             *         Integer castResult = (Integer) result;
-             *         int unboxedResult = castResult.intValue();
-             *         return unboxedResult;
-             *     }
-             *
-             * Or, in more idiomatic Java:
-             *
+             *     // if doSomething is not abstract
              *     public int doSomething(Bar param0, int param1) {
              *         if ($__handler == null) {
              *             return super.doSomething(param0, param1);
@@ -479,6 +506,15 @@
              *         return __handler.invoke(this, __methodArray[4],
              *                 new Object[] { param0, Integer.valueOf(param1) });
              *     }
+             *
+             *     // if doSomething is abstract
+             *     public int doSomething(Bar param0, int param1) {
+             *         if ($__handler == null) {
+             *             throw new AbstractMethodError("'doSomething' cannot be called");
+             *         }
+             *         return __handler.invoke(this, __methodArray[4],
+             *                 new Object[] { param0, Integer.valueOf(param1) });
+             *     }
              */
             Method method = methodsToProxy[m];
             String name = method.getName();
@@ -489,8 +525,9 @@
             }
             Class<?> returnType = method.getReturnType();
             TypeId<?> resultType = TypeId.get(returnType);
-            MethodId<T, ?> superMethod = superclassType.getMethod(resultType, name, argTypes);
             MethodId<?, ?> methodId = generatedType.getMethod(resultType, name, argTypes);
+            TypeId<AbstractMethodError> abstractMethodErrorClass =
+                    TypeId.get(AbstractMethodError.class);
             Code code = dexMaker.declare(methodId, PUBLIC);
             Local<G> localThis = code.getThis(generatedType);
             Local<InvocationHandler> localHandler = code.newLocal(handlerType);
@@ -508,10 +545,22 @@
             if (aBoxedClass != null) {
                 aBoxedResult = code.newLocal(TypeId.get(aBoxedClass));
             }
-            Local<?>[] superArgs2 = new Local<?>[argClasses.length];
-            Local<?> superResult2 = code.newLocal(resultType);
             Local<InvocationHandler> nullHandler = code.newLocal(handlerType);
 
+            Local<?>[] superArgs2 = null;
+            Local<?> superResult2 = null;
+            MethodId<T, ?> superMethod = null;
+            Local<String> abstractMethodErrorMessage = null;
+            Local<AbstractMethodError> abstractMethodError = null;
+            if ((method.getModifiers() & ABSTRACT) == 0) {
+                superArgs2 = new Local<?>[argClasses.length];
+                superResult2 = code.newLocal(resultType);
+                superMethod = superclassType.getMethod(resultType, name, argTypes);
+            } else {
+                abstractMethodErrorMessage = code.newLocal(TypeId.STRING);
+                abstractMethodError = code.newLocal(abstractMethodErrorClass);
+            }
+
             code.loadConstant(methodIndex, m);
             code.sget(allMethods, methodArray);
             code.aget(thisMethod, methodArray, methodIndex);
@@ -541,15 +590,21 @@
             // This is required to handle the case of construction of an object which leaks the
             // "this" pointer.
             code.mark(handlerNullCase);
-            for (int i = 0; i < superArgs2.length; ++i) {
-                superArgs2[i] = code.getParameter(i, argTypes[i]);
-            }
-            if (void.class.equals(returnType)) {
-                code.invokeSuper(superMethod, null, localThis, superArgs2);
-                code.returnVoid();
+
+            if ((method.getModifiers() & ABSTRACT) == 0) {
+                for (int i = 0; i < superArgs2.length; ++i) {
+                    superArgs2[i] = code.getParameter(i, argTypes[i]);
+                }
+                if (void.class.equals(returnType)) {
+                    code.invokeSuper(superMethod, null, localThis, superArgs2);
+                    code.returnVoid();
+                } else {
+                    invokeSuper(superMethod, code, localThis, superArgs2, superResult2);
+                    code.returnValue(superResult2);
+                }
             } else {
-                invokeSuper(superMethod, code, localThis, superArgs2, superResult2);
-                code.returnValue(superResult2);
+                throwAbstractMethodError(code, method, abstractMethodErrorMessage,
+                        abstractMethodError);
             }
 
             /*
@@ -560,22 +615,29 @@
              *          return result;
              *     }
              */
-            // TODO: don't include a super_ method if the target is abstract!
             MethodId<G, ?> callsSuperMethod = generatedType.getMethod(
                     resultType, superMethodName(method), argTypes);
             Code superCode = dexMaker.declare(callsSuperMethod, PUBLIC);
-            Local<G> superThis = superCode.getThis(generatedType);
-            Local<?>[] superArgs = new Local<?>[argClasses.length];
-            for (int i = 0; i < superArgs.length; ++i) {
-                superArgs[i] = superCode.getParameter(i, argTypes[i]);
-            }
-            if (void.class.equals(returnType)) {
-                superCode.invokeSuper(superMethod, null, superThis, superArgs);
-                superCode.returnVoid();
+            if ((method.getModifiers() & ABSTRACT) == 0) {
+                Local<G> superThis = superCode.getThis(generatedType);
+                Local<?>[] superArgs = new Local<?>[argClasses.length];
+                for (int i = 0; i < superArgs.length; ++i) {
+                    superArgs[i] = superCode.getParameter(i, argTypes[i]);
+                }
+                if (void.class.equals(returnType)) {
+                    superCode.invokeSuper(superMethod, null, superThis, superArgs);
+                    superCode.returnVoid();
+                } else {
+                    Local<?> superResult = superCode.newLocal(resultType);
+                    invokeSuper(superMethod, superCode, superThis, superArgs, superResult);
+                    superCode.returnValue(superResult);
+                }
             } else {
-                Local<?> superResult = superCode.newLocal(resultType);
-                invokeSuper(superMethod, superCode, superThis, superArgs, superResult);
-                superCode.returnValue(superResult);
+                Local<String> superAbstractMethodErrorMessage = superCode.newLocal(TypeId.STRING);
+                Local<AbstractMethodError> superAbstractMethodError = superCode.newLocal
+                        (abstractMethodErrorClass);
+                throwAbstractMethodError(superCode, method, superAbstractMethodErrorMessage,
+                        superAbstractMethodError);
             }
         }
     }
@@ -754,8 +816,9 @@
         }
     }
 
-    private static <T> String getMethodNameForProxyOf(Class<T> clazz) {
-        return clazz.getName().replace(".", "/") + "_Proxy";
+    private static <T> String getMethodNameForProxyOf(Class<T> clazz, List<Class<?>> interfaces) {
+        String interfacesHash = Integer.toHexString(interfaces.hashCode());
+        return clazz.getName().replace(".", "/") + "_" + interfacesHash + "_Proxy";
     }
 
     private static TypeId<?>[] classArrayToTypeArray(Class<?>[] input) {
@@ -789,10 +852,6 @@
         }
     }
 
-    private static <T> Set<T> asSet(T... array) {
-        return new CopyOnWriteArraySet<>(Arrays.asList(array));
-    }
-
     private static MethodId<?, ?> getUnboxMethodForPrimitive(Class<?> methodReturnType) {
         return PRIMITIVE_TO_UNBOX_METHOD.get(methodReturnType);
     }
@@ -883,4 +942,53 @@
             return result;
         }
     }
+
+    /**
+     * A class that was already proxied.
+     */
+    private static class ProxiedClass<U> {
+        final Class<U> clazz;
+
+        final List<Class<?>> interfaces;
+
+        /**
+         * Class loader requested when the proxy class was generated. This might not be the
+         * class loader of {@code clazz} as not all class loaders can be shared.
+         *
+         * @see DexMaker#generateClassLoader(File, File, ClassLoader)
+         */
+        final ClassLoader requestedClassloader;
+
+        final boolean sharedClassLoader;
+
+        @Override
+        public boolean equals(Object other) {
+            if (this == other) {
+                return true;
+            }
+            if (other == null || getClass() != other.getClass()) {
+                return false;
+            }
+
+            ProxiedClass<?> that = (ProxiedClass<?>) other;
+            return clazz == that.clazz
+                    && interfaces.equals(that.interfaces)
+                    && requestedClassloader == that.requestedClassloader
+                    && sharedClassLoader == that.sharedClassLoader;
+        }
+
+        @Override
+        public int hashCode() {
+            return clazz.hashCode() + interfaces.hashCode() + requestedClassloader.hashCode()
+                    + (sharedClassLoader ? 1 : 0); 
+        }
+
+        private ProxiedClass(Class<U> clazz, List<Class<?>> interfaces,
+                             ClassLoader requestedClassloader, boolean sharedClassLoader) {
+            this.clazz = clazz;
+            this.interfaces = new ArrayList<>(interfaces);
+            this.requestedClassloader = requestedClassloader;
+            this.sharedClassLoader = sharedClassLoader;
+        }
+    }
 }
diff --git a/lib/dalvik-dx-1-sources.jar b/lib/dalvik-dx-1-sources.jar
deleted file mode 100644
index 45cc845..0000000
--- a/lib/dalvik-dx-1-sources.jar
+++ /dev/null
Binary files differ
diff --git a/lib/dalvik-dx-1.jar b/lib/dalvik-dx-1.jar
deleted file mode 100644
index c559399..0000000
--- a/lib/dalvik-dx-1.jar
+++ /dev/null
Binary files differ
diff --git a/lib/dalvik-dx-9.0.0_r3-sources.jar b/lib/dalvik-dx-9.0.0_r3-sources.jar
new file mode 100644
index 0000000..4578bc7
--- /dev/null
+++ b/lib/dalvik-dx-9.0.0_r3-sources.jar
Binary files differ
diff --git a/lib/dalvik-dx-9.0.0_r3.jar b/lib/dalvik-dx-9.0.0_r3.jar
new file mode 100644
index 0000000..69f58e3
--- /dev/null
+++ b/lib/dalvik-dx-9.0.0_r3.jar
Binary files differ
diff --git a/lib/dalvik-dx-1.jar.txt b/lib/dalvik-dx-9.0.0_r3.jar.txt
similarity index 100%
rename from lib/dalvik-dx-1.jar.txt
rename to lib/dalvik-dx-9.0.0_r3.jar.txt
diff --git a/lib/libcore-dex-2-sources.jar b/lib/libcore-dex-2-sources.jar
deleted file mode 100644
index 3391687..0000000
--- a/lib/libcore-dex-2-sources.jar
+++ /dev/null
Binary files differ
diff --git a/lib/libcore-dex-2.jar b/lib/libcore-dex-2.jar
deleted file mode 100644
index 4d368d5..0000000
--- a/lib/libcore-dex-2.jar
+++ /dev/null
Binary files differ
diff --git a/lib/libcore-dex-2.jar.txt b/lib/libcore-dex-2.jar.txt
deleted file mode 100644
index e06d208..0000000
--- a/lib/libcore-dex-2.jar.txt
+++ /dev/null
@@ -1,202 +0,0 @@
-Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "{}"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright {yyyy} {name of copyright owner}
-
-   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.
-
diff --git a/update_source.sh b/update_source.sh
index ff7cc66..fc0c053 100755
--- a/update_source.sh
+++ b/update_source.sh
@@ -23,13 +23,18 @@
 SOURCE="https://github.com/linkedin/dexmaker"
 INCLUDE="
     LICENSE
+
     dexmaker
     dexmaker-mockito
-    dexmaker-mockito-tests
     dexmaker-mockito-inline
+    dexmaker-mockito-inline-extended
+
     dexmaker-mockito-inline-dispatcher
-    dexmaker-mockito-inline-tests/src
-    dexmaker-tests/src
+
+    dexmaker-tests
+    dexmaker-mockito-tests
+    dexmaker-mockito-inline-tests
+    dexmaker-mockito-inline-extended-tests
     "
 
 EXCLUDE="
@@ -58,11 +63,6 @@
   rm -r $exclude
 done;
 
-# Move the dexmaker-tests AndroidManifest.xml into the correct position.
-mv dexmaker-tests/src/main/AndroidManifest.xml dexmaker-tests/AndroidManifest.xml
-mv dexmaker-mockito-tests/src/main/AndroidManifest.xml dexmaker-mockito-tests/AndroidManifest.xml
-mv dexmaker-mockito-inline-tests/src/main/AndroidManifest.xml dexmaker-mockito-inline-tests/AndroidManifest.xml
-
 # Remove 3rd party code
 rm -r dexmaker-mockito-inline/external