Merge "Remove a.t.TestCase"
diff --git a/core/java/android/app/timezone/RulesState.java b/core/java/android/app/timezone/RulesState.java
index 33f4e80..7d6ad21 100644
--- a/core/java/android/app/timezone/RulesState.java
+++ b/core/java/android/app/timezone/RulesState.java
@@ -174,29 +174,14 @@
     }
 
     /**
-     * Returns true if the distro IANA rules version supplied is newer or the same as the version in
-     * the system image data files.
+     * Returns true if the system image data files contain IANA rules data that are newer than the
+     * distro IANA rules version supplied, i.e. true when the version specified would be "worse"
+     * than the one that is in the system image. Returns false if the system image version is the
+     * same or older, i.e. false when the version specified would be "better" than the one that is
+     * in the system image.
      */
-    public boolean isSystemVersionOlderThan(DistroRulesVersion distroRulesVersion) {
-        return mSystemRulesVersion.compareTo(distroRulesVersion.getRulesVersion()) < 0;
-    }
-
-    public boolean isDistroInstalled() {
-        return mDistroStatus == DISTRO_STATUS_INSTALLED;
-    }
-
-    /**
-     * Returns true if the rules version supplied is newer than the one currently installed. If
-     * there is no installed distro this method throws IllegalStateException.
-     */
-    public boolean isInstalledDistroOlderThan(DistroRulesVersion distroRulesVersion) {
-        if (mOperationInProgress) {
-            throw new IllegalStateException("Distro state not known: operation in progress.");
-        }
-        if (!isDistroInstalled()) {
-            throw new IllegalStateException("No distro installed.");
-        }
-        return mInstalledDistroRulesVersion.isOlderThan(distroRulesVersion);
+    public boolean isSystemVersionNewerThan(DistroRulesVersion distroRulesVersion) {
+        return mSystemRulesVersion.compareTo(distroRulesVersion.getRulesVersion()) > 0;
     }
 
     public static final Parcelable.Creator<RulesState> CREATOR =
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index ff52f27..838fb72 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -70,9 +70,10 @@
  * devices, and start a scan for Bluetooth LE devices.
  *
  * <p>To get a {@link BluetoothAdapter} representing the local Bluetooth
- * adapter, when running on JELLY_BEAN_MR1 and below, call the
- * static {@link #getDefaultAdapter} method; when running on JELLY_BEAN_MR2 and
- * higher, call {@link BluetoothManager#getAdapter}.
+ * adapter, call the {@link BluetoothManager#getAdapter} function on {@link BluetoothManager}.
+ * On JELLY_BEAN_MR1 and below you will need to use the static {@link #getDefaultAdapter}
+ * method instead.
+ * </p><p>
  * Fundamentally, this is your starting point for all
  * Bluetooth actions. Once you have the local adapter, you can get a set of
  * {@link BluetoothDevice} objects representing all paired devices with
@@ -81,14 +82,13 @@
  * listen for incoming connection requests with
  * {@link #listenUsingRfcommWithServiceRecord(String,UUID)}; or start a scan for
  * Bluetooth LE devices with {@link #startLeScan(LeScanCallback callback)}.
- *
- * <p>This class is thread safe.
- *
+ * </p>
+ * <p>This class is thread safe.</p>
  * <p class="note"><strong>Note:</strong>
  * Most methods require the {@link android.Manifest.permission#BLUETOOTH}
  * permission and some also require the
  * {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
- *
+ * </p>
  * <div class="special reference">
  * <h3>Developer Guides</h3>
  * <p>
@@ -565,6 +565,7 @@
      * <p>Currently Android only supports one Bluetooth adapter, but the API
      * could be extended to support more. This will always return the default
      * adapter.
+     * </p>
      * @return the default local adapter, or null if Bluetooth is not supported
      *         on this hardware platform
      */
diff --git a/core/java/android/bluetooth/BluetoothManager.java b/core/java/android/bluetooth/BluetoothManager.java
index 29283e7..4c21aae 100644
--- a/core/java/android/bluetooth/BluetoothManager.java
+++ b/core/java/android/bluetooth/BluetoothManager.java
@@ -32,10 +32,7 @@
  * Use {@link android.content.Context#getSystemService(java.lang.String)}
  * with {@link Context#BLUETOOTH_SERVICE} to create an {@link BluetoothManager},
  * then call {@link #getAdapter} to obtain the {@link BluetoothAdapter}.
- * <p>
- * Alternately, you can just call the static helper
- * {@link BluetoothAdapter#getDefaultAdapter()}.
- *
+ * </p>
  * <div class="special reference">
  * <h3>Developer Guides</h3>
  * <p>
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 1da0d28..2dd7f75 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -418,8 +418,16 @@
      */
     public static final int TRANSPORT_WIFI_AWARE = 5;
 
-    private static final int MIN_TRANSPORT = TRANSPORT_CELLULAR;
-    private static final int MAX_TRANSPORT = TRANSPORT_WIFI_AWARE;
+    /**
+     * Indicates this network uses a LoWPAN transport.
+     * @hide
+     */
+    public static final int TRANSPORT_LOWPAN = 6;
+
+    /** @hide */
+    public static final int MIN_TRANSPORT = TRANSPORT_CELLULAR;
+    /** @hide */
+    public static final int MAX_TRANSPORT = TRANSPORT_LOWPAN;
 
     private static final String[] TRANSPORT_NAMES = {
         "CELLULAR",
@@ -427,7 +435,8 @@
         "BLUETOOTH",
         "ETHERNET",
         "VPN",
-        "WIFI_AWARE"
+        "WIFI_AWARE",
+        "LOWPAN"
     };
 
     /**
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 50a3f4c..65d05a8 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -529,7 +529,7 @@
     
     protected void finalize() throws Throwable {
         try {
-            destroy();
+            destroyBinder();
         } finally {
             super.finalize();
         }
@@ -559,7 +559,7 @@
     }
 
     private native final void init();
-    private native final void destroy();
+    private native final void destroyBinder();
 
     // Entry point from android_util_Binder.cpp's onTransact
     private boolean execTransact(int code, long dataObj, long replyObj,
diff --git a/core/java/com/android/internal/os/WrapperInit.java b/core/java/com/android/internal/os/WrapperInit.java
index aced75d..1a556d5 100644
--- a/core/java/com/android/internal/os/WrapperInit.java
+++ b/core/java/com/android/internal/os/WrapperInit.java
@@ -160,6 +160,8 @@
             argv = removedArgs;
         }
 
+        // Perform the same initialization that would happen after the Zygote forks.
+        Zygote.nativePreApplicationInit();
         RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
     }
 
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index e065843..91d9d1e 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -121,6 +121,11 @@
           int[] fdsToIgnore, String instructionSet, String appDataDir);
 
     /**
+     * Called to do any initialization before starting an application.
+     */
+    native static void nativePreApplicationInit();
+
+    /**
      * Special method to start the system server process. In addition to the
      * common actions performed in forkAndSpecialize, the pid of the child
      * process is recorded such that the death of the child process will cause
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index a5f7d4f..2006be0 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -207,7 +207,6 @@
     static_libs: [
         "libseccomp_policy",
         "libselinux",
-        "libcrypto",
     ],
 
     shared_libs: [
diff --git a/core/jni/android/opengl/util.cpp b/core/jni/android/opengl/util.cpp
index d4735ec..41d9111 100644
--- a/core/jni/android/opengl/util.cpp
+++ b/core/jni/android/opengl/util.cpp
@@ -49,27 +49,6 @@
     pDest[3] = pM[3 + 4 * 0] * x + pM[3 + 4 * 1] * y + pM[3 + 4 * 2] * z + pM[3 + 4 * 3] * w;
 }
 
-class MallocHelper {
-public:
-    MallocHelper() {
-        mData = 0;
-    }
-
-    ~MallocHelper() {
-        if (mData != 0) {
-            free(mData);
-        }
-    }
-
-    void* alloc(size_t size) {
-        mData = malloc(size);
-        return mData;
-    }
-
-private:
-    void* mData;
-};
-
 #if 0
 static
 void
@@ -85,10 +64,7 @@
 static
 int visibilityTest(float* pWS, float* pPositions, int positionsLength,
         unsigned short* pIndices, int indexCount) {
-    MallocHelper mallocHelper;
     int result = POLY_CLIP_OUT;
-    float* pTransformed = 0;
-    int transformedIndexCount = 0;
 
     if ( indexCount < 3 ) {
         return POLY_CLIP_OUT;
@@ -116,8 +92,9 @@
         return -1;
     }
 
-    transformedIndexCount = maxIndex - minIndex + 1;
-    pTransformed = (float*) mallocHelper.alloc(transformedIndexCount * 4 * sizeof(float));
+    int transformedIndexCount = maxIndex - minIndex + 1;
+    std::unique_ptr<float[]> holder{new float[transformedIndexCount * 4]};
+    float* pTransformed = holder.get();
 
     if (pTransformed == 0 ) {
         return -2;
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 26b0034..e2aa17b 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -836,7 +836,7 @@
     env->SetLongField(obj, gBinderOffsets.mObject, (jlong)jbh);
 }
 
-static void android_os_Binder_destroy(JNIEnv* env, jobject obj)
+static void android_os_Binder_destroyBinder(JNIEnv* env, jobject obj)
 {
     JavaBBinderHolder* jbh = (JavaBBinderHolder*)
         env->GetLongField(obj, gBinderOffsets.mObject);
@@ -847,7 +847,7 @@
     } else {
         // Encountering an uninitialized binder is harmless.  All it means is that
         // the Binder was only partially initialized when its finalizer ran and called
-        // destroy().  The Binder could be partially initialized for several reasons.
+        // destroyBinder().  The Binder could be partially initialized for several reasons.
         // For example, a Binder subclass constructor might have thrown an exception before
         // it could delegate to its superclass's constructor.  Consequently init() would
         // not have been called and the holder pointer would remain NULL.
@@ -872,7 +872,7 @@
     { "getThreadStrictModePolicy", "()I", (void*)android_os_Binder_getThreadStrictModePolicy },
     { "flushPendingCommands", "()V", (void*)android_os_Binder_flushPendingCommands },
     { "init", "()V", (void*)android_os_Binder_init },
-    { "destroy", "()V", (void*)android_os_Binder_destroy },
+    { "destroyBinder", "()V", (void*)android_os_Binder_destroyBinder },
     { "blockUntilThreadAvailable", "()V", (void*)android_os_Binder_blockUntilThreadAvailable }
 };
 
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index d73e7dd..cb53106 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -221,6 +221,14 @@
 // The debug malloc library needs to know whether it's the zygote or a child.
 extern "C" int gMallocLeakZygoteChild;
 
+static void PreApplicationInit() {
+  // The child process sets this to indicate it's not the zygote.
+  gMallocLeakZygoteChild = 1;
+
+  // Set the jemalloc decay time to 1.
+  mallopt(M_DECAY_TIME, 1);
+}
+
 static void EnableKeepCapabilities(JNIEnv* env) {
   int rc = prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0);
   if (rc == -1) {
@@ -517,11 +525,7 @@
   pid_t pid = fork();
 
   if (pid == 0) {
-    // The child process.
-    gMallocLeakZygoteChild = 1;
-
-    // Set the jemalloc decay time to 1.
-    mallopt(M_DECAY_TIME, 1);
+    PreApplicationInit();
 
     // Clean up any descriptors which must be closed immediately
     DetachDescriptors(env, fdsToClose);
@@ -678,6 +682,10 @@
 
 namespace android {
 
+static void com_android_internal_os_Zygote_nativePreApplicationInit(JNIEnv*, jclass) {
+  PreApplicationInit();
+}
+
 static jint com_android_internal_os_Zygote_nativeForkAndSpecialize(
         JNIEnv* env, jclass, jint uid, jint gid, jintArray gids,
         jint debug_flags, jobjectArray rlimits,
@@ -807,7 +815,9 @@
     { "nativeAllowFileAcrossFork", "(Ljava/lang/String;)V",
       (void *) com_android_internal_os_Zygote_nativeAllowFileAcrossFork },
     { "nativeUnmountStorageOnInit", "()V",
-      (void *) com_android_internal_os_Zygote_nativeUnmountStorageOnInit }
+      (void *) com_android_internal_os_Zygote_nativeUnmountStorageOnInit },
+    { "nativePreApplicationInit", "()V",
+      (void *) com_android_internal_os_Zygote_nativePreApplicationInit }
 };
 
 int register_com_android_internal_os_Zygote(JNIEnv* env) {
diff --git a/core/tests/coretests/src/android/app/timezone/RulesStateTest.java b/core/tests/coretests/src/android/app/timezone/RulesStateTest.java
index a9357c9..7f4819b 100644
--- a/core/tests/coretests/src/android/app/timezone/RulesStateTest.java
+++ b/core/tests/coretests/src/android/app/timezone/RulesStateTest.java
@@ -107,7 +107,7 @@
                 "2016a", formatVersion(1, 1), true /* operationInProgress */,
                 RulesState.STAGED_OPERATION_UNKNOWN, null /* stagedDistroRulesVersion */,
                 RulesState.DISTRO_STATUS_UNKNOWN, null /* installedDistroRulesVersion */);
-        checkParcelableRoundTrip(rulesStateWithNulls);
+        checkParcelableRoundTrip(rulesStateWithUnknowns);
     }
 
     private static void checkParcelableRoundTrip(RulesState rulesState) {
@@ -121,55 +121,14 @@
     }
 
     @Test
-    public void isSystemVersionOlderThan() {
+    public void isSystemVersionNewerThan() {
         RulesState rulesState = new RulesState(
                 "2016b", formatVersion(1, 1), false /* operationInProgress */,
                 RulesState.STAGED_OPERATION_NONE, null /* stagedDistroRulesVersion */,
                 RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 3));
-        assertFalse(rulesState.isSystemVersionOlderThan(rulesVersion("2016a", 1)));
-        assertFalse(rulesState.isSystemVersionOlderThan(rulesVersion("2016b", 1)));
-        assertTrue(rulesState.isSystemVersionOlderThan(rulesVersion("2016c", 1)));
-    }
-
-    @Test
-    public void isInstalledDistroOlderThan() {
-        RulesState operationInProgress = new RulesState(
-                "2016b", formatVersion(1, 1), true /* operationInProgress */,
-                RulesState.STAGED_OPERATION_UNKNOWN, null /* stagedDistroRulesVersion */,
-                RulesState.STAGED_OPERATION_UNKNOWN, null /* installedDistroRulesVersion */);
-        try {
-            operationInProgress.isInstalledDistroOlderThan(rulesVersion("2016b", 1));
-            fail();
-        } catch (IllegalStateException expected) {
-        }
-
-        RulesState nothingInstalled = new RulesState(
-                "2016b", formatVersion(1, 1), false /* operationInProgress */,
-                RulesState.STAGED_OPERATION_NONE, null /* stagedDistroRulesVersion */,
-                RulesState.DISTRO_STATUS_NONE, null /* installedDistroRulesVersion */);
-        try {
-            nothingInstalled.isInstalledDistroOlderThan(rulesVersion("2016b", 1));
-            fail();
-        } catch (IllegalStateException expected) {
-        }
-
-        DistroRulesVersion installedVersion = rulesVersion("2016b", 3);
-        RulesState rulesStateWithInstalledVersion = new RulesState(
-                "2016b", formatVersion(1, 1), false /* operationInProgress */,
-                RulesState.STAGED_OPERATION_NONE, null /* stagedDistroRulesVersion */,
-                RulesState.DISTRO_STATUS_INSTALLED, installedVersion);
-
-        DistroRulesVersion olderRules = rulesVersion("2016a", 1);
-        assertEquals(installedVersion.isOlderThan(olderRules),
-                rulesStateWithInstalledVersion.isInstalledDistroOlderThan(olderRules));
-
-        DistroRulesVersion sameRules = rulesVersion("2016b", 1);
-        assertEquals(installedVersion.isOlderThan(sameRules),
-                rulesStateWithInstalledVersion.isInstalledDistroOlderThan(sameRules));
-
-        DistroRulesVersion newerRules = rulesVersion("2016c", 1);
-        assertEquals(installedVersion.isOlderThan(newerRules),
-                rulesStateWithInstalledVersion.isInstalledDistroOlderThan(newerRules));
+        assertTrue(rulesState.isSystemVersionNewerThan(rulesVersion("2016a", 1)));
+        assertFalse(rulesState.isSystemVersionNewerThan(rulesVersion("2016b", 1)));
+        assertFalse(rulesState.isSystemVersionNewerThan(rulesVersion("2016c", 1)));
     }
 
     private static void assertEqualsContract(RulesState one, RulesState two) {
diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java
index 7b57bbd..9e2d696 100644
--- a/services/net/java/android/net/ip/IpManager.java
+++ b/services/net/java/android/net/ip/IpManager.java
@@ -714,7 +714,6 @@
         return shouldLog;
     }
 
-    // TODO: Migrate all Log.e(...) to logError(...).
     private void logError(String fmt, Object... args) {
         final String msg = "ERROR " + String.format(fmt, args);
         Log.e(mTag, msg);
@@ -1035,7 +1034,7 @@
     }
 
     private void doImmediateProvisioningFailure(int failureType) {
-        if (DBG) { Log.e(mTag, "onProvisioningFailure(): " + failureType); }
+        logError("onProvisioningFailure(): %s", failureType);
         recordMetric(failureType);
         mCallback.onProvisioningFailure(new LinkProperties(mLinkProperties));
     }
@@ -1170,7 +1169,7 @@
 
                 case DhcpClient.CMD_ON_QUIT:
                     // Everything is already stopped.
-                    Log.e(mTag, "Unexpected CMD_ON_QUIT (already stopped).");
+                    logError("Unexpected CMD_ON_QUIT (already stopped).");
                     break;
 
                 default:
@@ -1376,7 +1375,7 @@
                     break;
 
                 case CMD_START:
-                    Log.e(mTag, "ALERT: START received in StartedState. Please fix caller.");
+                    logError("ALERT: START received in StartedState. Please fix caller.");
                     break;
 
                 case CMD_CONFIRM:
@@ -1475,13 +1474,13 @@
                             handleIPv4Failure();
                             break;
                         default:
-                            Log.e(mTag, "Unknown CMD_POST_DHCP_ACTION status:" + msg.arg1);
+                            logError("Unknown CMD_POST_DHCP_ACTION status: %s", msg.arg1);
                     }
                     break;
 
                 case DhcpClient.CMD_ON_QUIT:
                     // DHCPv4 quit early for some reason.
-                    Log.e(mTag, "Unexpected CMD_ON_QUIT.");
+                    logError("Unexpected CMD_ON_QUIT.");
                     mDhcpClient = null;
                     break;
 
diff --git a/test-runner/src/android/test/TestCaseUtil.java b/test-runner/src/android/test/TestCaseUtil.java
index dc053a2..15629099 100644
--- a/test-runner/src/android/test/TestCaseUtil.java
+++ b/test-runner/src/android/test/TestCaseUtil.java
@@ -40,16 +40,6 @@
     private TestCaseUtil() {
     }
 
-    @SuppressWarnings("unchecked")
-    public static List<String> getTestCaseNames(Test test, boolean flatten) {
-        List<Test> tests = (List<Test>) getTests(test, flatten);
-        List<String> testCaseNames = new ArrayList<>();
-        for (Test aTest : tests) {
-            testCaseNames.add(getTestName(aTest));
-        }
-        return testCaseNames;
-    }
-
     public static List<? extends Test> getTests(Test test, boolean flatten) {
         return getTests(test, flatten, new HashSet<Class<?>>());
     }
@@ -92,7 +82,7 @@
         return testCases;
     }
 
-    private static Test invokeSuiteMethodIfPossible(Class testClass,
+    static Test invokeSuiteMethodIfPossible(Class testClass,
             Set<Class<?>> seen) {
         try {
             Method suiteMethod = testClass.getMethod(
@@ -120,7 +110,7 @@
         return null;
     }
 
-    public static String getTestName(Test test) {
+    static String getTestName(Test test) {
         if (test instanceof TestCase) {
             TestCase testCase = (TestCase) test;
             return testCase.getName();
@@ -138,34 +128,4 @@
         }
         return "";
     }
-
-    public static Test getTestAtIndex(TestSuite testSuite, int position) {
-        int index = 0;
-        Enumeration enumeration = testSuite.tests();
-        while (enumeration.hasMoreElements()) {
-            Test test = (Test) enumeration.nextElement();
-            if (index == position) {
-                return test;
-            }
-            index++;
-        }
-        return null;
-    }
-
-    public static TestSuite createTestSuite(Class<? extends Test> testClass)
-            throws InstantiationException, IllegalAccessException {
-
-        Test test = invokeSuiteMethodIfPossible(testClass,
-                new HashSet<Class<?>>());
-        if (test == null) {
-            return new TestSuite(testClass);
-
-        } else if (TestCase.class.isAssignableFrom(test.getClass())) {
-            TestSuite testSuite = new TestSuite(test.getClass().getName());
-            testSuite.addTest(test);
-            return testSuite;
-        }
-
-        return (TestSuite) test;
-    }
 }
diff --git a/test-runner/src/android/test/TestPrinter.java b/test-runner/src/android/test/TestPrinter.java
index a23f06d..01d392d 100644
--- a/test-runner/src/android/test/TestPrinter.java
+++ b/test-runner/src/android/test/TestPrinter.java
@@ -21,7 +21,6 @@
 import junit.framework.TestListener;
 
 import java.util.HashSet;
-import java.util.List;
 import java.util.Set;
 
 /**
@@ -34,52 +33,37 @@
  * {@hide} Not needed for 1.0 SDK.
  */
 @Deprecated
-public class TestPrinter implements TestRunner.Listener, TestListener {
+class TestPrinter implements TestListener {
 
     private String mTag;
     private boolean mOnlyFailures;
     private Set<String> mFailedTests = new HashSet<String>();
 
 
-    public TestPrinter(String tag, boolean onlyFailures) {
+    TestPrinter(String tag, boolean onlyFailures) {
         mTag = tag;
         mOnlyFailures = onlyFailures;
     }
 
-    public void started(String className) {
+    private void started(String className) {
         if (!mOnlyFailures) {
             Log.i(mTag, "started: " + className);
         }
     }
 
-    public void finished(String className) {
+    private void finished(String className) {
         if (!mOnlyFailures) {
             Log.i(mTag, "finished: " + className);
         }
     }
 
-    public void performance(String className,
-            long itemTimeNS, int iterations,
-            List<TestRunner.IntermediateTime> intermediates) {
-        Log.i(mTag, "perf: " + className + " = " + itemTimeNS + "ns/op (done "
-                + iterations + " times)");
-        if (intermediates != null && intermediates.size() > 0) {
-            int N = intermediates.size();
-            for (int i = 0; i < N; i++) {
-                TestRunner.IntermediateTime time = intermediates.get(i);
-                Log.i(mTag, "  intermediate: " + time.name + " = "
-                        + time.timeInNS + "ns");
-            }
-        }
-    }
-
-    public void passed(String className) {
+    private void passed(String className) {
         if (!mOnlyFailures) {
             Log.i(mTag, "passed: " + className);
         }
     }
 
-    public void failed(String className, Throwable exception) {
+    private void failed(String className, Throwable exception) {
         Log.i(mTag, "failed: " + className);
         Log.i(mTag, "----- begin exception -----");
         Log.i(mTag, "", exception);
diff --git a/test-runner/src/android/test/TestRunner.java b/test-runner/src/android/test/TestRunner.java
deleted file mode 100644
index ff045c3..0000000
--- a/test-runner/src/android/test/TestRunner.java
+++ /dev/null
@@ -1,725 +0,0 @@
-/*
- * Copyright (C) 2006 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 android.test;
-
-import android.content.Context;
-import android.util.Log;
-import android.os.Debug;
-import android.os.SystemClock;
-
-import java.io.File;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.util.ArrayList;
-import java.util.List;
-
-import junit.framework.TestSuite;
-import junit.framework.TestListener;
-import junit.framework.Test;
-import junit.framework.TestResult;
-
-/**
- * Support class that actually runs a test. Android uses this class,
- * and you probably will not need to instantiate, extend, or call this
- * class yourself. See the full {@link android.test} package description
- * to learn more about testing Android applications.
- *
- * {@hide} Not needed for 1.0 SDK.
- */
-@Deprecated
-public class TestRunner implements PerformanceTestCase.Intermediates {
-    public static final int REGRESSION = 0;
-    public static final int PERFORMANCE = 1;
-    public static final int PROFILING = 2;
-
-    public static final int CLEARSCREEN = 0;
-    private static final String TAG = "TestHarness";
-    private Context mContext;
-
-    private int mMode = REGRESSION;
-
-    private List<Listener> mListeners = new ArrayList<>();
-    private int mPassed;
-    private int mFailed;
-
-    private int mInternalIterations;
-    private long mStartTime;
-    private long mEndTime;
-
-    private String mClassName;
-
-    List<IntermediateTime> mIntermediates = null;
-
-    private static Class mRunnableClass;
-    private static Class mJUnitClass;
-
-    static {
-        try {
-            mRunnableClass = Class.forName("java.lang.Runnable", false, null);
-            mJUnitClass = Class.forName("junit.framework.TestCase", false, null);
-        } catch (ClassNotFoundException ex) {
-            throw new RuntimeException("shouldn't happen", ex);
-        }
-    }
-
-    public class JunitTestSuite extends TestSuite implements TestListener {
-        boolean mError = false;
-
-        public JunitTestSuite() {
-            super();
-        }
-
-        @Override
-        public void run(TestResult result) {
-            result.addListener(this);
-            super.run(result);
-            result.removeListener(this);
-        }
-
-        /**
-         * Implemented method of the interface TestListener which will listen for the
-         * start of a test.
-         *
-         * @param test
-         */
-        public void startTest(Test test) {
-            started(test.toString());
-        }
-
-        /**
-         * Implemented method of the interface TestListener which will listen for the
-         * end of the test.
-         *
-         * @param test
-         */
-        public void endTest(Test test) {
-            finished(test.toString());
-            if (!mError) {
-                passed(test.toString());
-            }
-        }
-
-        /**
-         * Implemented method of the interface TestListener which will listen for an
-         * mError while running the test.
-         *
-         * @param test
-         */
-        public void addError(Test test, Throwable t) {
-            mError = true;
-            failed(test.toString(), t);
-        }
-
-        public void addFailure(Test test, junit.framework.AssertionFailedError t) {
-            mError = true;
-            failed(test.toString(), t);
-        }
-    }
-
-    /**
-     * Listener.performance() 'intermediates' argument is a list of these.
-     */
-    public static class IntermediateTime {
-        public IntermediateTime(String name, long timeInNS) {
-            this.name = name;
-            this.timeInNS = timeInNS;
-        }
-
-        public String name;
-        public long timeInNS;
-    }
-
-    /**
-     * Support class that receives status on test progress. You should not need to
-     * extend this interface yourself.
-     */
-    public interface Listener {
-        void started(String className);
-        void finished(String className);
-        void performance(String className,
-                long itemTimeNS, int iterations,
-                List<IntermediateTime> itermediates);
-        void passed(String className);
-        void failed(String className, Throwable execption);
-    }
-
-    public TestRunner(Context context) {
-        mContext = context;
-    }
-
-    public void addListener(Listener listener) {
-        mListeners.add(listener);
-    }
-
-    public void startProfiling() {
-        File file = new File("/tmp/trace");
-        file.mkdir();
-        String base = "/tmp/trace/" + mClassName + ".dmtrace";
-        Debug.startMethodTracing(base, 8 * 1024 * 1024);
-    }
-
-    public void finishProfiling() {
-        Debug.stopMethodTracing();
-    }
-
-    private void started(String className) {
-
-        int count = mListeners.size();
-        for (int i = 0; i < count; i++) {
-            mListeners.get(i).started(className);
-        }
-    }
-
-    private void finished(String className) {
-        int count = mListeners.size();
-        for (int i = 0; i < count; i++) {
-            mListeners.get(i).finished(className);
-        }
-    }
-
-    private void performance(String className,
-            long itemTimeNS,
-            int iterations,
-            List<IntermediateTime> intermediates) {
-        int count = mListeners.size();
-        for (int i = 0; i < count; i++) {
-            mListeners.get(i).performance(className,
-                    itemTimeNS,
-                    iterations,
-                    intermediates);
-        }
-    }
-
-    public void passed(String className) {
-        mPassed++;
-        int count = mListeners.size();
-        for (int i = 0; i < count; i++) {
-            mListeners.get(i).passed(className);
-        }
-    }
-
-    public void failed(String className, Throwable exception) {
-        mFailed++;
-        int count = mListeners.size();
-        for (int i = 0; i < count; i++) {
-            mListeners.get(i).failed(className, exception);
-        }
-    }
-
-    public int passedCount() {
-        return mPassed;
-    }
-
-    public int failedCount() {
-        return mFailed;
-    }
-
-    public void run(String[] classes) {
-        for (String cl : classes) {
-            run(cl);
-        }
-    }
-
-    public void setInternalIterations(int count) {
-        mInternalIterations = count;
-    }
-
-    public void startTiming(boolean realTime) {
-        if (realTime) {
-            mStartTime = System.currentTimeMillis();
-        } else {
-            mStartTime = SystemClock.currentThreadTimeMillis();
-        }
-    }
-
-    public void addIntermediate(String name) {
-        addIntermediate(name, (System.currentTimeMillis() - mStartTime) * 1000000);
-    }
-
-    public void addIntermediate(String name, long timeInNS) {
-        mIntermediates.add(new IntermediateTime(name, timeInNS));
-    }
-
-    public void finishTiming(boolean realTime) {
-        if (realTime) {
-            mEndTime = System.currentTimeMillis();
-        } else {
-            mEndTime = SystemClock.currentThreadTimeMillis();
-        }
-    }
-
-    public void setPerformanceMode(int mode) {
-        mMode = mode;
-    }
-
-    private void missingTest(String className, Throwable e) {
-        started(className);
-        finished(className);
-        failed(className, e);
-    }
-
-    /*
-    This class determines if more suites are added to this class then adds all individual
-    test classes to a test suite for run
-     */
-    public void run(String className) {
-        try {
-            mClassName = className;
-            Class clazz = mContext.getClassLoader().loadClass(className);
-            Method method = getChildrenMethod(clazz);
-            if (method != null) {
-                String[] children = getChildren(method);
-                run(children);
-            } else if (mRunnableClass.isAssignableFrom(clazz)) {
-                Runnable test = (Runnable) clazz.newInstance();
-                TestCase testcase = null;
-                if (test instanceof TestCase) {
-                    testcase = (TestCase) test;
-                }
-                Throwable e = null;
-                boolean didSetup = false;
-                started(className);
-                try {
-                    if (testcase != null) {
-                        testcase.setUp(mContext);
-                        didSetup = true;
-                    }
-                    if (mMode == PERFORMANCE) {
-                        runInPerformanceMode(test, className, false, className);
-                    } else if (mMode == PROFILING) {
-                        //Need a way to mark a test to be run in profiling mode or not.
-                        startProfiling();
-                        test.run();
-                        finishProfiling();
-                    } else {
-                        test.run();
-                    }
-                } catch (Throwable ex) {
-                    e = ex;
-                }
-                if (testcase != null && didSetup) {
-                    try {
-                        testcase.tearDown();
-                    } catch (Throwable ex) {
-                        e = ex;
-                    }
-                }
-                finished(className);
-                if (e == null) {
-                    passed(className);
-                } else {
-                    failed(className, e);
-                }
-            } else if (mJUnitClass.isAssignableFrom(clazz)) {
-                Throwable e = null;
-                //Create a Junit Suite.
-                JunitTestSuite suite = new JunitTestSuite();
-                Method[] methods = getAllTestMethods(clazz);
-                for (Method m : methods) {
-                    junit.framework.TestCase test = (junit.framework.TestCase) clazz.newInstance();
-                    test.setName(m.getName());
-
-                    if (test instanceof AndroidTestCase) {
-                        AndroidTestCase testcase = (AndroidTestCase) test;
-                        try {
-                            testcase.setContext(mContext);
-                            testcase.setTestContext(mContext);
-                        } catch (Exception ex) {
-                            Log.i("TestHarness", ex.toString());
-                        }
-                    }
-                    suite.addTest(test);
-                }
-                if (mMode == PERFORMANCE) {
-                    final int testCount = suite.testCount();
-
-                    for (int j = 0; j < testCount; j++) {
-                        Test test = suite.testAt(j);
-                        started(test.toString());
-                        try {
-                            runInPerformanceMode(test, className, true, test.toString());
-                        } catch (Throwable ex) {
-                            e = ex;
-                        }
-                        finished(test.toString());
-                        if (e == null) {
-                            passed(test.toString());
-                        } else {
-                            failed(test.toString(), e);
-                        }
-                    }
-                } else if (mMode == PROFILING) {
-                    //Need a way to mark a test to be run in profiling mode or not.
-                    startProfiling();
-                    junit.textui.TestRunner.run(suite);
-                    finishProfiling();
-                } else {
-                    junit.textui.TestRunner.run(suite);
-                }
-            } else {
-                System.out.println("Test wasn't Runnable and didn't have a"
-                        + " children method: " + className);
-            }
-        } catch (ClassNotFoundException e) {
-            Log.e("ClassNotFoundException for " + className, e.toString());
-            if (isJunitTest(className)) {
-                runSingleJunitTest(className);
-            } else {
-                missingTest(className, e);
-            }
-        } catch (InstantiationException e) {
-            System.out.println("InstantiationException for " + className);
-            missingTest(className, e);
-        } catch (IllegalAccessException e) {
-            System.out.println("IllegalAccessException for " + className);
-            missingTest(className, e);
-        }
-    }
-
-    public void runInPerformanceMode(Object testCase, String className, boolean junitTest,
-            String testNameInDb) throws Exception {
-        boolean increaseIterations = true;
-        int iterations = 1;
-        long duration = 0;
-        mIntermediates = null;
-
-        mInternalIterations = 1;
-        Class clazz = mContext.getClassLoader().loadClass(className);
-        Object perftest = clazz.newInstance();
-
-        PerformanceTestCase perftestcase = null;
-        if (perftest instanceof PerformanceTestCase) {
-            perftestcase = (PerformanceTestCase) perftest;
-            // only run the test if it is not marked as a performance only test
-            if (mMode == REGRESSION && perftestcase.isPerformanceOnly()) return;
-        }
-
-        // First force GCs, to avoid GCs happening during out
-        // test and skewing its time.
-        Runtime.getRuntime().runFinalization();
-        Runtime.getRuntime().gc();
-
-        if (perftestcase != null) {
-            mIntermediates = new ArrayList<IntermediateTime>();
-            iterations = perftestcase.startPerformance(this);
-            if (iterations > 0) {
-                increaseIterations = false;
-            } else {
-                iterations = 1;
-            }
-        }
-
-        // Pause briefly to let things settle down...
-        Thread.sleep(1000);
-        do {
-            mEndTime = 0;
-            if (increaseIterations) {
-                // Test case does not implement
-                // PerformanceTestCase or returned 0 iterations,
-                // so we take care of measure the whole test time.
-                mStartTime = SystemClock.currentThreadTimeMillis();
-            } else {
-                // Try to make it obvious if the test case
-                // doesn't call startTiming().
-                mStartTime = 0;
-            }
-
-            if (junitTest) {
-                for (int i = 0; i < iterations; i++) {
-                    junit.textui.TestRunner.run((junit.framework.Test) testCase);
-                }
-            } else {
-                Runnable test = (Runnable) testCase;
-                for (int i = 0; i < iterations; i++) {
-                    test.run();
-                }
-            }
-
-            long endTime = mEndTime;
-            if (endTime == 0) {
-                endTime = SystemClock.currentThreadTimeMillis();
-            }
-
-            duration = endTime - mStartTime;
-            if (!increaseIterations) {
-                break;
-            }
-            if (duration <= 1) {
-                iterations *= 1000;
-            } else if (duration <= 10) {
-                iterations *= 100;
-            } else if (duration < 100) {
-                iterations *= 10;
-            } else if (duration < 1000) {
-                iterations *= (int) ((1000 / duration) + 2);
-            } else {
-                break;
-            }
-        } while (true);
-
-        if (duration != 0) {
-            iterations *= mInternalIterations;
-            performance(testNameInDb, (duration * 1000000) / iterations,
-                    iterations, mIntermediates);
-        }
-    }
-
-    public void runSingleJunitTest(String className) {
-        Throwable excep = null;
-        int index = className.lastIndexOf('$');
-        String testName = "";
-        String originalClassName = className;
-        if (index >= 0) {
-            className = className.substring(0, index);
-            testName = originalClassName.substring(index + 1);
-        }
-        try {
-            Class clazz = mContext.getClassLoader().loadClass(className);
-            if (mJUnitClass.isAssignableFrom(clazz)) {
-                junit.framework.TestCase test = (junit.framework.TestCase) clazz.newInstance();
-                JunitTestSuite newSuite = new JunitTestSuite();
-                test.setName(testName);
-
-                if (test instanceof AndroidTestCase) {
-                    AndroidTestCase testcase = (AndroidTestCase) test;
-                    try {
-                        testcase.setContext(mContext);
-                    } catch (Exception ex) {
-                        Log.w(TAG, "Exception encountered while trying to set the context.", ex);
-                    }
-                }
-                newSuite.addTest(test);
-
-                if (mMode == PERFORMANCE) {
-                    try {
-                        started(test.toString());
-                        runInPerformanceMode(test, className, true, test.toString());
-                        finished(test.toString());
-                        if (excep == null) {
-                            passed(test.toString());
-                        } else {
-                            failed(test.toString(), excep);
-                        }
-                    } catch (Throwable ex) {
-                        excep = ex;
-                    }
-
-                } else if (mMode == PROFILING) {
-                    startProfiling();
-                    junit.textui.TestRunner.run(newSuite);
-                    finishProfiling();
-                } else {
-                    junit.textui.TestRunner.run(newSuite);
-                }
-            }
-        } catch (ClassNotFoundException e) {
-            Log.e("TestHarness", "No test case to run", e);
-        } catch (IllegalAccessException e) {
-            Log.e("TestHarness", "Illegal Access Exception", e);
-        } catch (InstantiationException e) {
-            Log.e("TestHarness", "Instantiation Exception", e);
-        }
-    }
-
-    public static Method getChildrenMethod(Class clazz) {
-        try {
-            return clazz.getMethod("children", (Class[]) null);
-        } catch (NoSuchMethodException e) {
-        }
-
-        return null;
-    }
-
-    public static Method getChildrenMethod(Context c, String className) {
-        try {
-            return getChildrenMethod(c.getClassLoader().loadClass(className));
-        } catch (ClassNotFoundException e) {
-        }
-        return null;
-    }
-
-    public static String[] getChildren(Context c, String className) {
-        Method m = getChildrenMethod(c, className);
-        String[] testChildren = getTestChildren(c, className);
-        if (m == null & testChildren == null) {
-            throw new RuntimeException("couldn't get children method for "
-                    + className);
-        }
-        if (m != null) {
-            String[] children = getChildren(m);
-            if (testChildren != null) {
-                String[] allChildren = new String[testChildren.length + children.length];
-                System.arraycopy(children, 0, allChildren, 0, children.length);
-                System.arraycopy(testChildren, 0, allChildren, children.length, testChildren.length);
-                return allChildren;
-            } else {
-                return children;
-            }
-        } else {
-            if (testChildren != null) {
-                return testChildren;
-            }
-        }
-        return null;
-    }
-
-    public static String[] getChildren(Method m) {
-        try {
-            if (!Modifier.isStatic(m.getModifiers())) {
-                throw new RuntimeException("children method is not static");
-            }
-            return (String[]) m.invoke(null, (Object[]) null);
-        } catch (IllegalAccessException e) {
-        } catch (InvocationTargetException e) {
-        }
-        return new String[0];
-    }
-
-    public static String[] getTestChildren(Context c, String className) {
-        try {
-            Class clazz = c.getClassLoader().loadClass(className);
-
-            if (mJUnitClass.isAssignableFrom(clazz)) {
-                return getTestChildren(clazz);
-            }
-        } catch (ClassNotFoundException e) {
-            Log.e("TestHarness", "No class found", e);
-        }
-        return null;
-    }
-
-    public static String[] getTestChildren(Class clazz) {
-        Method[] methods = getAllTestMethods(clazz);
-
-        String[] onScreenTestNames = new String[methods.length];
-        int index = 0;
-        for (Method m : methods) {
-            onScreenTestNames[index] = clazz.getName() + "$" + m.getName();
-            index++;
-        }
-        return onScreenTestNames;
-    }
-
-    public static Method[] getAllTestMethods(Class clazz) {
-        Method[] allMethods = clazz.getDeclaredMethods();
-        int numOfMethods = 0;
-        for (Method m : allMethods) {
-            boolean mTrue = isTestMethod(m);
-            if (mTrue) {
-                numOfMethods++;
-            }
-        }
-        int index = 0;
-        Method[] testMethods = new Method[numOfMethods];
-        for (Method m : allMethods) {
-            boolean mTrue = isTestMethod(m);
-            if (mTrue) {
-                testMethods[index] = m;
-                index++;
-            }
-        }
-        return testMethods;
-    }
-
-    private static boolean isTestMethod(Method m) {
-        return m.getName().startsWith("test") &&
-                m.getReturnType() == void.class &&
-                m.getParameterTypes().length == 0;
-    }
-
-    public static int countJunitTests(Class clazz) {
-        Method[] allTestMethods = getAllTestMethods(clazz);
-        int numberofMethods = allTestMethods.length;
-
-        return numberofMethods;
-    }
-
-    public static boolean isTestSuite(Context c, String className) {
-        boolean childrenMethods = getChildrenMethod(c, className) != null;
-
-        try {
-            Class clazz = c.getClassLoader().loadClass(className);
-            if (mJUnitClass.isAssignableFrom(clazz)) {
-                int numTests = countJunitTests(clazz);
-                if (numTests > 0)
-                    childrenMethods = true;
-            }
-        } catch (ClassNotFoundException e) {
-        }
-        return childrenMethods;
-    }
-
-
-    public boolean isJunitTest(String className) {
-        int index = className.lastIndexOf('$');
-        if (index >= 0) {
-            className = className.substring(0, index);
-        }
-        try {
-            Class clazz = mContext.getClassLoader().loadClass(className);
-            if (mJUnitClass.isAssignableFrom(clazz)) {
-                return true;
-            }
-        } catch (ClassNotFoundException e) {
-        }
-        return false;
-    }
-
-    /**
-     * Returns the number of tests that will be run if you try to do this.
-     */
-    public static int countTests(Context c, String className) {
-        try {
-            Class clazz = c.getClassLoader().loadClass(className);
-            Method method = getChildrenMethod(clazz);
-            if (method != null) {
-
-                String[] children = getChildren(method);
-                int rv = 0;
-                for (String child : children) {
-                    rv += countTests(c, child);
-                }
-                return rv;
-            } else if (mRunnableClass.isAssignableFrom(clazz)) {
-                return 1;
-            } else if (mJUnitClass.isAssignableFrom(clazz)) {
-                return countJunitTests(clazz);
-            }
-        } catch (ClassNotFoundException e) {
-            return 1; // this gets the count right, because either this test
-            // is missing, and it will fail when run or it is a single Junit test to be run.
-        }
-        return 0;
-    }
-
-    /**
-     * Returns a title to display given the className of a test.
-     * <p/>
-     * <p>Currently this function just returns the portion of the
-     * class name after the last '.'
-     */
-    public static String getTitle(String className) {
-        int indexDot = className.lastIndexOf('.');
-        int indexDollar = className.lastIndexOf('$');
-        int index = indexDot > indexDollar ? indexDot : indexDollar;
-        if (index >= 0) {
-            className = className.substring(index + 1);
-        }
-        return className;
-    }
-}
diff --git a/test-runner/tests/src/android/test/TestCaseUtilTest.java b/test-runner/tests/src/android/test/TestCaseUtilTest.java
index 9d12eaf..6d424b0 100644
--- a/test-runner/tests/src/android/test/TestCaseUtilTest.java
+++ b/test-runner/tests/src/android/test/TestCaseUtilTest.java
@@ -16,6 +16,8 @@
 
 package android.test;
 
+import java.util.ArrayList;
+import java.util.HashSet;
 import junit.framework.Test;
 import junit.framework.TestCase;
 import junit.framework.TestSuite;
@@ -24,38 +26,50 @@
 
 public class TestCaseUtilTest extends TestCase {
 
-    public void testGetTestCaseNamesForTestSuiteWithSuiteMethod() throws Exception {
+    @SuppressWarnings("unchecked")
+    private static List<String> getTestCaseNames(Test test) {
+        List<Test> tests = (List<Test>) TestCaseUtil.getTests(test, false);
+        List<String> testCaseNames = new ArrayList<>();
+        for (Test aTest : tests) {
+            testCaseNames.add(TestCaseUtil.getTestName(aTest));
+        }
+        return testCaseNames;
+    }
+
+    public void testGetTests_ForTestSuiteWithSuiteMethod() throws Exception {
         TestSuite testSuite = new TwoTestsInTestSuite();
 
-        List<String> testCaseNames = TestCaseUtil.getTestCaseNames(testSuite, false);
+        List<String> testCaseNames = getTestCaseNames(testSuite);
 
         assertEquals(0, testCaseNames.size());
     }
     
-    public void testGetTestCaseNamesForTestCaseWithSuiteMethod() throws Exception {
+    public void testGetTests_ForTestCaseWithSuiteMethod() throws Exception {
         TestCase testCase = new OneTestTestCaseWithSuite();
 
-        List<String> testCaseNames = TestCaseUtil.getTestCaseNames(testCase, false);
+        List<String> testCaseNames = getTestCaseNames(testCase);
 
         assertEquals(1, testCaseNames.size());
         assertTrue(testCaseNames.get(0).endsWith("testOne"));
     }
 
-    public void testCreateTestForTestCase() throws Exception {
-        Test test = TestCaseUtil.createTestSuite(OneTestTestCase.class);
-        assertEquals(1, test.countTestCases());
+    public void testInvokeSuiteMethodIfPossible_ForTestCase() throws Exception {
+        Test test = TestCaseUtil.invokeSuiteMethodIfPossible(OneTestTestCase.class, new HashSet<>());
+        assertNull(test);
     }
-    
-    public void testCreateTestForTestSuiteWithSuiteMethod() throws Exception {
-        Test test = TestCaseUtil.createTestSuite(TwoTestsInTestSuite.class);
+
+    public void testInvokeSuiteMethodIfPossible_ForTestSuiteWithSuiteMethod() throws Exception {
+        Test test = TestCaseUtil.invokeSuiteMethodIfPossible(TwoTestsInTestSuite.class, new HashSet<>());
+        assertNotNull(test);
         assertEquals(2, test.countTestCases());
     }
 
-    public void testCreateTestForTestCaseWithSuiteMethod() throws Exception {
-        Test test = TestCaseUtil.createTestSuite(OneTestTestCaseWithSuite.class);
+    public void testInvokeSuiteMethodIfPossible_ForTestCaseWithSuiteMethod() throws Exception {
+        Test test = TestCaseUtil.invokeSuiteMethodIfPossible(OneTestTestCaseWithSuite.class, new HashSet<>());
+        assertNotNull(test);
         assertEquals(1, test.countTestCases());
     }
-    
+
     public void testReturnEmptyStringForTestSuiteWithNoName() throws Exception {
         assertEquals("", TestCaseUtil.getTestName(new TestSuite()));
     }
diff --git a/tests/net/java/android/net/util/SharedLogTest.java b/tests/net/java/android/net/util/SharedLogTest.java
index 7fd7a63..3957cb0 100644
--- a/tests/net/java/android/net/util/SharedLogTest.java
+++ b/tests/net/java/android/net/util/SharedLogTest.java
@@ -33,9 +33,8 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class SharedLogTest {
-    private static final String TIMESTAMP_PATTERN =
-            "^[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]";
-    private static final String TIMESTAMP = "mm-dd HH:MM:SS.xxx";
+    private static final String TIMESTAMP_PATTERN = "\\d{2}:\\d{2}:\\d{2}\\.\\d{3}";
+    private static final String TIMESTAMP = "HH:MM:SS.xxx";
 
     @Test
     public void testBasicOperation() {
@@ -53,12 +52,12 @@
         logLevel2a.mark("ok: last post");
 
         final String[] expected = {
-            TIMESTAMP + " - MARK first post!",
-            TIMESTAMP + " - [twoB] ERROR 2b or not 2b",
-            TIMESTAMP + " - [twoA] WARN second post?",
-            TIMESTAMP + " - still logging",
-            TIMESTAMP + " - [twoA.three] 3 >> 2",
-            TIMESTAMP + " - [twoA] MARK ok: last post",
+            " - MARK first post!",
+            " - [twoB] ERROR 2b or not 2b",
+            " - [twoA] WARN second post?",
+            " - still logging",
+            " - [twoA.three] 3 >> 2",
+            " - [twoA] MARK ok: last post",
         };
         // Verify the logs are all there and in the correct order.
         verifyLogLines(expected, logTop);
@@ -82,13 +81,12 @@
         final String[] lines = dumpOutput.split("\n");
         assertEquals(expected.length, lines.length);
 
-        for (int i = 0; i < lines.length; i++) {
-            // Fix up the timestamps.
-            lines[i] = lines[i].replaceAll(TIMESTAMP_PATTERN, TIMESTAMP);
-        }
-
         for (int i = 0; i < expected.length; i++) {
-            assertEquals(expected[i], lines[i]);
+            String got = lines[i];
+            String want = expected[i];
+            assertTrue(String.format("'%s' did not contain '%s'", got, want), got.endsWith(want));
+            assertTrue(String.format("'%s' did not contain a HH:MM:SS.xxx timestamp", got),
+                    got.replaceFirst(TIMESTAMP_PATTERN, TIMESTAMP).contains(TIMESTAMP));
         }
     }
 }
diff --git a/tools/locked_region_code_injection/Android.mk b/tools/locked_region_code_injection/Android.mk
new file mode 100644
index 0000000..0aed0ce
--- /dev/null
+++ b/tools/locked_region_code_injection/Android.mk
@@ -0,0 +1,15 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_JAR_MANIFEST := manifest.txt
+LOCAL_MODULE := lockedregioncodeinjection
+LOCAL_SRC_FILES := $(call all-java-files-under,src)
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    asm-5.2 \
+    asm-commons-5.2 \
+    asm-tree-5.2 \
+    asm-analysis-5.2
+
+
+include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/tools/locked_region_code_injection/manifest.txt b/tools/locked_region_code_injection/manifest.txt
new file mode 100644
index 0000000..4b9de00
--- /dev/null
+++ b/tools/locked_region_code_injection/manifest.txt
@@ -0,0 +1 @@
+Main-Class: lockedregioncodeinjection.Main
diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java
new file mode 100644
index 0000000..9374f23
--- /dev/null
+++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java
@@ -0,0 +1,241 @@
+/*
+ * 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 lockedregioncodeinjection;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.commons.TryCatchBlockSorter;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.InsnList;
+import org.objectweb.asm.tree.LabelNode;
+import org.objectweb.asm.tree.MethodInsnNode;
+import org.objectweb.asm.tree.MethodNode;
+import org.objectweb.asm.tree.TryCatchBlockNode;
+import org.objectweb.asm.tree.analysis.Analyzer;
+import org.objectweb.asm.tree.analysis.AnalyzerException;
+import org.objectweb.asm.tree.analysis.BasicValue;
+import org.objectweb.asm.tree.analysis.Frame;
+
+/**
+ * This visitor does two things:
+ *
+ * 1. Finds all the MONITOR_ENTER / MONITOR_EXIT in the byte code and insert the corresponding pre
+ * and post methods calls should it matches one of the given target type in the Configuration.
+ *
+ * 2. Find all methods that are synchronized and insert pre method calls in the beginning and post
+ * method calls just before all return instructions.
+ */
+class LockFindingClassVisitor extends ClassVisitor {
+    private String className = null;
+    private final List<LockTarget> targets;
+
+    public LockFindingClassVisitor(List<LockTarget> targets, ClassVisitor chain) {
+        super(Utils.ASM_VERSION, chain);
+        this.targets = targets;
+    }
+
+    @Override
+    public MethodVisitor visitMethod(int access, String name, String desc, String signature,
+            String[] exceptions) {
+        assert this.className != null;
+        MethodNode mn = new TryCatchBlockSorter(null, access, name, desc, signature, exceptions);
+        MethodVisitor chain = super.visitMethod(access, name, desc, signature, exceptions);
+        return new LockFindingMethodVisitor(this.className, mn, chain);
+    }
+
+    @Override
+    public void visit(int version, int access, String name, String signature, String superName,
+            String[] interfaces) {
+        this.className = name;
+        super.visit(version, access, name, signature, superName, interfaces);
+    }
+
+    class LockFindingMethodVisitor extends MethodVisitor {
+        private String owner;
+        private MethodVisitor chain;
+
+        public LockFindingMethodVisitor(String owner, MethodNode mn, MethodVisitor chain) {
+            super(Opcodes.ASM5, mn);
+            assert owner != null;
+            this.owner = owner;
+            this.chain = chain;
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public void visitEnd() {
+            MethodNode mn = (MethodNode) mv;
+
+            Analyzer a = new Analyzer(new LockTargetStateAnalysis(targets));
+
+            LockTarget ownerMonitor = null;
+            if ((mn.access & Opcodes.ACC_SYNCHRONIZED) != 0) {
+                for (LockTarget t : targets) {
+                    if (t.getTargetDesc().equals("L" + owner + ";")) {
+                        ownerMonitor = t;
+                    }
+                }
+            }
+
+            try {
+                a.analyze(owner, mn);
+            } catch (AnalyzerException e) {
+                e.printStackTrace();
+            }
+            InsnList instructions = mn.instructions;
+
+            Frame[] frames = a.getFrames();
+            List<Frame> frameMap = new LinkedList<>();
+            frameMap.addAll(Arrays.asList(frames));
+
+            List<List<TryCatchBlockNode>> handlersMap = new LinkedList<>();
+
+            for (int i = 0; i < instructions.size(); i++) {
+                handlersMap.add(a.getHandlers(i));
+            }
+
+            if (ownerMonitor != null) {
+                AbstractInsnNode s = instructions.getFirst();
+                MethodInsnNode call = new MethodInsnNode(Opcodes.INVOKESTATIC,
+                        ownerMonitor.getPreOwner(), ownerMonitor.getPreMethod(), "()V", false);
+                insertMethodCallBefore(mn, frameMap, handlersMap, s, 0, call);
+            }
+
+            for (int i = 0; i < instructions.size(); i++) {
+                AbstractInsnNode s = instructions.get(i);
+
+                if (s.getOpcode() == Opcodes.MONITORENTER) {
+                    Frame f = frameMap.get(i);
+                    BasicValue operand = (BasicValue) f.getStack(f.getStackSize() - 1);
+                    if (operand instanceof LockTargetState) {
+                        LockTargetState state = (LockTargetState) operand;
+                        for (int j = 0; j < state.getTargets().size(); j++) {
+                            LockTarget target = state.getTargets().get(j);
+                            MethodInsnNode call = new MethodInsnNode(Opcodes.INVOKESTATIC,
+                                    target.getPreOwner(), target.getPreMethod(), "()V", false);
+                            insertMethodCallAfter(mn, frameMap, handlersMap, s, i, call);
+                        }
+                    }
+                }
+
+                if (s.getOpcode() == Opcodes.MONITOREXIT) {
+                    Frame f = frameMap.get(i);
+                    BasicValue operand = (BasicValue) f.getStack(f.getStackSize() - 1);
+                    if (operand instanceof LockTargetState) {
+                        LockTargetState state = (LockTargetState) operand;
+                        for (int j = 0; j < state.getTargets().size(); j++) {
+                            LockTarget target = state.getTargets().get(j);
+                            MethodInsnNode call = new MethodInsnNode(Opcodes.INVOKESTATIC,
+                                    target.getPostOwner(), target.getPostMethod(), "()V", false);
+                            insertMethodCallAfter(mn, frameMap, handlersMap, s, i, call);
+                        }
+                    }
+                }
+
+                if (ownerMonitor != null && (s.getOpcode() == Opcodes.RETURN
+                        || s.getOpcode() == Opcodes.ARETURN || s.getOpcode() == Opcodes.DRETURN
+                        || s.getOpcode() == Opcodes.FRETURN || s.getOpcode() == Opcodes.IRETURN)) {
+                    MethodInsnNode call =
+                            new MethodInsnNode(Opcodes.INVOKESTATIC, ownerMonitor.getPostOwner(),
+                                    ownerMonitor.getPostMethod(), "()V", false);
+                    insertMethodCallBefore(mn, frameMap, handlersMap, s, i, call);
+                    i++; // Skip ahead. Otherwise, we will revisit this instruction again.
+                }
+            }
+            super.visitEnd();
+            mn.accept(chain);
+        }
+    }
+
+    public static void insertMethodCallBefore(MethodNode mn, List<Frame> frameMap,
+            List<List<TryCatchBlockNode>> handlersMap, AbstractInsnNode node, int index,
+            MethodInsnNode call) {
+        List<TryCatchBlockNode> handlers = handlersMap.get(index);
+        InsnList instructions = mn.instructions;
+        LabelNode end = new LabelNode();
+        instructions.insert(node, end);
+        frameMap.add(index, null);
+        handlersMap.add(index, null);
+        instructions.insertBefore(node, call);
+        frameMap.add(index, null);
+        handlersMap.add(index, null);
+
+        LabelNode start = new LabelNode();
+        instructions.insert(node, start);
+        frameMap.add(index, null);
+        handlersMap.add(index, null);
+        updateCatchHandler(mn, handlers, start, end, handlersMap);
+    }
+
+    public static void insertMethodCallAfter(MethodNode mn, List<Frame> frameMap,
+            List<List<TryCatchBlockNode>> handlersMap, AbstractInsnNode node, int index,
+            MethodInsnNode call) {
+        List<TryCatchBlockNode> handlers = handlersMap.get(index + 1);
+        InsnList instructions = mn.instructions;
+
+        LabelNode end = new LabelNode();
+        instructions.insert(node, end);
+        frameMap.add(index + 1, null);
+        handlersMap.add(index + 1, null);
+
+        instructions.insert(node, call);
+        frameMap.add(index + 1, null);
+        handlersMap.add(index + 1, null);
+
+        LabelNode start = new LabelNode();
+        instructions.insert(node, start);
+        frameMap.add(index + 1, null);
+        handlersMap.add(index + 1, null);
+
+        updateCatchHandler(mn, handlers, start, end, handlersMap);
+    }
+
+    @SuppressWarnings("unchecked")
+    public static void updateCatchHandler(MethodNode mn, List<TryCatchBlockNode> handlers,
+            LabelNode start, LabelNode end, List<List<TryCatchBlockNode>> handlersMap) {
+        if (handlers == null || handlers.size() == 0) {
+            return;
+        }
+
+        InsnList instructions = mn.instructions;
+        List<TryCatchBlockNode> newNodes = new ArrayList<>(handlers.size());
+        for (TryCatchBlockNode handler : handlers) {
+            if (!(instructions.indexOf(handler.start) <= instructions.indexOf(start)
+                    && instructions.indexOf(end) <= instructions.indexOf(handler.end))) {
+                TryCatchBlockNode newNode =
+                        new TryCatchBlockNode(start, end, handler.handler, handler.type);
+                newNodes.add(newNode);
+                for (int i = instructions.indexOf(start); i <= instructions.indexOf(end); i++) {
+                    if (handlersMap.get(i) == null) {
+                        handlersMap.set(i, new ArrayList<>());
+                    }
+                    handlersMap.get(i).add(newNode);
+                }
+            } else {
+                for (int i = instructions.indexOf(start); i <= instructions.indexOf(end); i++) {
+                    if (handlersMap.get(i) == null) {
+                        handlersMap.set(i, new ArrayList<>());
+                    }
+                    handlersMap.get(i).add(handler);
+                }
+            }
+        }
+        mn.tryCatchBlocks.addAll(0, newNodes);
+    }
+}
diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTarget.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTarget.java
new file mode 100644
index 0000000..c5e59e3
--- /dev/null
+++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTarget.java
@@ -0,0 +1,61 @@
+/*
+ * 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 lockedregioncodeinjection;
+
+/**
+ * Represent a specific class that is used for synchronization. A pre and post method can be
+ * specified to by the user to be called right after monitor_enter and after monitor_exit
+ * respectively.
+ */
+public class LockTarget {
+    public static final LockTarget NO_TARGET = new LockTarget("", null, null);
+
+    private final String targetDesc;
+    private final String pre;
+    private final String post;
+
+    public LockTarget(String targetDesc, String pre, String post) {
+        this.targetDesc = targetDesc;
+        this.pre = pre;
+        this.post = post;
+    }
+
+    public String getTargetDesc() {
+        return targetDesc;
+    }
+
+    public String getPre() {
+        return pre;
+    }
+
+    public String getPreOwner() {
+        return pre.substring(0, pre.lastIndexOf('.'));
+    }
+
+    public String getPreMethod() {
+        return pre.substring(pre.lastIndexOf('.') + 1);
+    }
+
+    public String getPost() {
+        return post;
+    }
+
+    public String getPostOwner() {
+        return post.substring(0, post.lastIndexOf('.'));
+    }
+
+    public String getPostMethod() {
+        return post.substring(post.lastIndexOf('.') + 1);
+    }
+}
diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetState.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetState.java
new file mode 100644
index 0000000..99d8418
--- /dev/null
+++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetState.java
@@ -0,0 +1,34 @@
+/*
+ * 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 lockedregioncodeinjection;
+
+import java.util.List;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.tree.analysis.BasicValue;
+
+public class LockTargetState extends BasicValue {
+    private final List<LockTarget> lockTargets;
+
+    /**
+     * @param type
+     */
+    public LockTargetState(Type type, List<LockTarget> lockTargets) {
+        super(type);
+        this.lockTargets = lockTargets;
+    }
+
+    public List<LockTarget> getTargets() {
+        return lockTargets;
+    }
+}
diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetStateAnalysis.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetStateAnalysis.java
new file mode 100644
index 0000000..1002c88
--- /dev/null
+++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetStateAnalysis.java
@@ -0,0 +1,109 @@
+/*
+ * 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 lockedregioncodeinjection;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.MethodInsnNode;
+import org.objectweb.asm.tree.analysis.AnalyzerException;
+import org.objectweb.asm.tree.analysis.BasicInterpreter;
+import org.objectweb.asm.tree.analysis.BasicValue;
+
+/**
+ * A simple dataflow analysis to determine if the operands on the stack must be one of target lock
+ * class type.
+ */
+public class LockTargetStateAnalysis extends BasicInterpreter {
+
+    private final List<LockTarget> targetLocks;
+
+    public LockTargetStateAnalysis(List<LockTarget> targetLocks) {
+        this.targetLocks = targetLocks;
+    }
+
+    @Override
+    public BasicValue naryOperation(AbstractInsnNode inst, @SuppressWarnings("rawtypes") List args)
+            throws AnalyzerException {
+        // We target the return type of any invocation.
+
+        @SuppressWarnings("unchecked")
+        BasicValue base = super.naryOperation(inst, args);
+        if (!(inst instanceof MethodInsnNode)) {
+            return base;
+        }
+
+        MethodInsnNode invoke = (MethodInsnNode) inst;
+        Type returnType = Type.getReturnType(invoke.desc);
+        if (returnType.equals(Type.VOID_TYPE)) {
+            return base;
+        }
+
+        List<LockTarget> types = new ArrayList<>();
+
+        for (LockTarget target : targetLocks) {
+            if (returnType.getDescriptor().equals(target.getTargetDesc())) {
+                types.add(target);
+            }
+        }
+
+        return new LockTargetState(base.getType(), types);
+    }
+
+    @Override
+    public BasicValue newValue(Type type) {
+        BasicValue base = super.newValue(type);
+        List<LockTarget> types = new ArrayList<>();
+
+        if (type == null) {
+            return base;
+        }
+        for (LockTarget target : targetLocks) {
+            if (type.getDescriptor().equals(target.getTargetDesc())) {
+                types.add(target);
+            }
+        }
+
+        if (types.isEmpty()) {
+            return base;
+        }
+
+        return new LockTargetState(base.getType(), types);
+    }
+
+    @Override
+    public BasicValue merge(BasicValue v1, BasicValue v2) {
+        BasicValue base = super.merge(v1, v2);
+
+        if (!(v1 instanceof LockTargetState)) {
+            return base;
+        }
+        if (!(v2 instanceof LockTargetState)) {
+            return base;
+        }
+
+        LockTargetState state1 = (LockTargetState) v1;
+        LockTargetState state2 = (LockTargetState) v2;
+
+        List<LockTarget> newList = new ArrayList<>(state1.getTargets());
+        for (LockTarget otherTarget : state2.getTargets()) {
+            if (!newList.contains(otherTarget)) {
+                newList.add(otherTarget);
+            }
+        }
+
+        return new LockTargetState(base.getType(), newList);
+    }
+}
diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/Main.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/Main.java
new file mode 100644
index 0000000..edb9a49
--- /dev/null
+++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/Main.java
@@ -0,0 +1,104 @@
+/*
+ * 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 lockedregioncodeinjection;
+
+import java.io.BufferedInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassWriter;
+
+public class Main {
+    public static void main(String[] args) throws IOException {
+        String inJar = null;
+        String outJar = null;
+
+        String legacyTargets = null;
+        String legacyPreMethods = null;
+        String legacyPostMethods = null;
+        for (int i = 0; i < args.length; i++) {
+            if ("-i".equals(args[i].trim())) {
+                i++;
+                inJar = args[i].trim();
+            } else if ("-o".equals(args[i].trim())) {
+                i++;
+                outJar = args[i].trim();
+            } else if ("--targets".equals(args[i].trim())) {
+                i++;
+                legacyTargets = args[i].trim();
+            } else if ("--pre".equals(args[i].trim())) {
+                i++;
+                legacyPreMethods = args[i].trim();
+            } else if ("--post".equals(args[i].trim())) {
+                i++;
+                legacyPostMethods = args[i].trim();
+            }
+
+        }
+
+        // TODO(acleung): Better help message than asserts.
+        assert inJar != null;
+        assert outJar != null;
+        assert legacyTargets == null || (legacyPreMethods != null && legacyPostMethods != null);
+
+        ZipFile zipSrc = new ZipFile(inJar);
+        ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(outJar));
+        List<LockTarget> targets = null;
+        if (legacyTargets != null) {
+            targets = Utils.getTargetsFromLegacyJackConfig(legacyTargets, legacyPreMethods,
+                    legacyPostMethods);
+        } else {
+            targets = Collections.emptyList();
+        }
+
+        Enumeration<? extends ZipEntry> srcEntries = zipSrc.entries();
+        while (srcEntries.hasMoreElements()) {
+            ZipEntry entry = srcEntries.nextElement();
+            ZipEntry newEntry = new ZipEntry(entry.getName());
+            zos.putNextEntry(newEntry);
+            BufferedInputStream bis = new BufferedInputStream(zipSrc.getInputStream(entry));
+
+            if (entry.getName().endsWith(".class")) {
+                convert(bis, zos, targets);
+            } else {
+                while (bis.available() > 0) {
+                    zos.write(bis.read());
+                }
+                zos.closeEntry();
+                bis.close();
+            }
+        }
+        zos.finish();
+        zos.close();
+        zipSrc.close();
+    }
+
+    private static void convert(InputStream in, OutputStream out, List<LockTarget> targets)
+            throws IOException {
+        ClassReader cr = new ClassReader(in);
+        ClassWriter cw = new ClassWriter(0);
+        LockFindingClassVisitor cv = new LockFindingClassVisitor(targets, cw);
+        cr.accept(cv, 0);
+        byte[] data = cw.toByteArray();
+        out.write(data);
+    }
+}
diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java
new file mode 100644
index 0000000..d2a2e7b
--- /dev/null
+++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java
@@ -0,0 +1,46 @@
+/*
+ * 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 lockedregioncodeinjection;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.objectweb.asm.Opcodes;
+
+public class Utils {
+
+    public static final int ASM_VERSION = Opcodes.ASM5;
+
+    /**
+     * Reads a comma separated configuration similar to the Jack definition.
+     */
+    public static List<LockTarget> getTargetsFromLegacyJackConfig(String classList,
+            String requestList, String resetList) {
+
+        String[] classes = classList.split(",");
+        String[] requests = requestList.split(",");
+        String[] resets = resetList.split(",");
+
+        int total = classes.length;
+        assert requests.length == total;
+        assert resets.length == total;
+
+        List<LockTarget> config = new ArrayList<LockTarget>();
+
+        for (int i = 0; i < total; i++) {
+            config.add(new LockTarget(classes[i], requests[i], resets[i]));
+        }
+
+        return config;
+    }
+}
diff --git a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java
new file mode 100644
index 0000000..1d4f2d4
--- /dev/null
+++ b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java
@@ -0,0 +1,231 @@
+/*
+ * 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 lockedregioncodeinjection;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * To run the unit tests:
+ *
+ * <pre>
+ * <code>
+ * set -x
+ *
+ * # Clean
+ * rm -fr out/*
+ *
+ * # Make booster
+ * javac -cp lib/asm-all-5.2.jar src&#47;*&#47;*.java -d out/
+ * pushd out
+ * jar cfe lockedregioncodeinjection.jar lockedregioncodeinjection.Main *&#47;*.class
+ * popd
+ *
+ * # Make unit tests.
+ * javac -cp lib/junit-4.12.jar test&#47;*&#47;*.java -d out/
+ *
+ * pushd out
+ * jar cfe test_input.jar lockedregioncodeinjection.Test *&#47;*.class
+ * popd
+ *
+ * # Run tool on unit tests.
+ * java -ea -cp lib/asm-all-5.2.jar:out/lockedregioncodeinjection.jar \
+ *     lockedregioncodeinjection.Main \
+ *     -i out/test_input.jar -o out/test_output.jar \
+ *     --targets 'Llockedregioncodeinjection/TestTarget;' \
+ *     --pre     'lockedregioncodeinjection/TestTarget.boost' \
+ *     --post    'lockedregioncodeinjection/TestTarget.unboost'
+ *
+ * # Run unit tests.
+ * java -ea -cp lib/hamcrest-core-1.3.jar:lib/junit-4.12.jar:out/test_output.jar \
+ *     org.junit.runner.JUnitCore lockedregioncodeinjection.TestMain
+ * </code>
+ * </pre>
+ */
+public class TestMain {
+    @Test
+    public void testSimpleSynchronizedBlock() {
+        TestTarget.resetCount();
+        TestTarget t = new TestTarget();
+
+        Assert.assertEquals(TestTarget.boostCount, 0);
+        Assert.assertEquals(TestTarget.unboostCount, 0);
+        Assert.assertEquals(TestTarget.unboostCount, 0);
+
+        synchronized (t) {
+            Assert.assertEquals(TestTarget.boostCount, 1);
+            Assert.assertEquals(TestTarget.unboostCount, 0);
+            TestTarget.invoke();
+        }
+
+        Assert.assertEquals(TestTarget.boostCount, 1);
+        Assert.assertEquals(TestTarget.unboostCount, 1);
+        Assert.assertEquals(TestTarget.invokeCount, 1);
+    }
+
+    @Test
+    public void testSimpleSynchronizedMethod() {
+        TestTarget.resetCount();
+        TestTarget t = new TestTarget();
+
+        Assert.assertEquals(TestTarget.boostCount, 0);
+        Assert.assertEquals(TestTarget.unboostCount, 0);
+
+        t.synchronizedCall();
+
+        Assert.assertEquals(TestTarget.boostCount, 1);
+        Assert.assertEquals(TestTarget.unboostCount, 1);
+        Assert.assertEquals(TestTarget.invokeCount, 1);
+    }
+
+    @Test
+    public void testSimpleSynchronizedMethod2() {
+        TestTarget.resetCount();
+        TestTarget t = new TestTarget();
+
+        Assert.assertEquals(TestTarget.boostCount, 0);
+        Assert.assertEquals(TestTarget.unboostCount, 0);
+
+        t.synchronizedCallReturnInt();
+
+        Assert.assertEquals(TestTarget.boostCount, 1);
+        Assert.assertEquals(TestTarget.unboostCount, 1);
+        Assert.assertEquals(TestTarget.invokeCount, 1);
+    }
+
+    @Test
+    public void testSimpleSynchronizedMethod3() {
+        TestTarget.resetCount();
+        TestTarget t = new TestTarget();
+
+        Assert.assertEquals(TestTarget.boostCount, 0);
+        Assert.assertEquals(TestTarget.unboostCount, 0);
+
+        t.synchronizedCallReturnObject();
+
+        Assert.assertEquals(TestTarget.boostCount, 1);
+        Assert.assertEquals(TestTarget.unboostCount, 1);
+        Assert.assertEquals(TestTarget.invokeCount, 1);
+    }
+
+    @SuppressWarnings("unused")
+    @Test
+    public void testCaughtException() {
+        TestTarget.resetCount();
+        TestTarget t = new TestTarget();
+        boolean caughtException = false;
+
+        Assert.assertEquals(TestTarget.boostCount, 0);
+        Assert.assertEquals(TestTarget.unboostCount, 0);
+        Assert.assertEquals(TestTarget.unboostCount, 0);
+
+        try {
+            synchronized (t) {
+                Assert.assertEquals(TestTarget.boostCount, 1);
+                Assert.assertEquals(TestTarget.unboostCount, 0);
+                if (true) {
+                    throw new RuntimeException();
+                }
+                TestTarget.invoke();
+            }
+        } catch (Throwable e) {
+            caughtException = true;
+        }
+
+        Assert.assertEquals(TestTarget.boostCount, 1);
+        Assert.assertEquals(TestTarget.unboostCount, 1);
+        Assert.assertEquals(TestTarget.invokeCount, 0); // Not called
+        Assert.assertTrue(caughtException);
+    }
+
+    @SuppressWarnings("unused")
+    private void testUncaughtException() {
+        TestTarget t = new TestTarget();
+        synchronized (t) {
+            if (true) {
+                throw new RuntimeException();
+            }
+            TestTarget.invoke();
+        }
+    }
+
+    @SuppressWarnings("unused")
+    @Test
+    public void testHandledFinally() {
+        TestTarget.resetCount();
+        try {
+            testUncaughtException();
+        } catch (Throwable t) {
+
+        }
+        Assert.assertEquals(TestTarget.boostCount, 1);
+        Assert.assertEquals(TestTarget.unboostCount, 1);
+        Assert.assertEquals(TestTarget.invokeCount, 0); // Not called
+    }
+
+    @Test
+    public void testNestedSynchronizedBlock() {
+        TestTarget.resetCount();
+        TestTarget t = new TestTarget();
+
+        Assert.assertEquals(TestTarget.boostCount, 0);
+        Assert.assertEquals(TestTarget.unboostCount, 0);
+        Assert.assertEquals(TestTarget.unboostCount, 0);
+
+        synchronized (t) {
+            synchronized (t) {
+                synchronized (t) {
+                    synchronized (t) {
+                        synchronized (t) {
+                            synchronized (t) {
+                                Assert.assertEquals(TestTarget.boostCount, 6);
+                                Assert.assertEquals(TestTarget.unboostCount, 0);
+                                TestTarget.invoke();
+                            }
+                            Assert.assertEquals(TestTarget.unboostCount, 1);
+                        }
+                        Assert.assertEquals(TestTarget.unboostCount, 2);
+                    }
+                    Assert.assertEquals(TestTarget.unboostCount, 3);
+                }
+                Assert.assertEquals(TestTarget.unboostCount, 4);
+            }
+            Assert.assertEquals(TestTarget.unboostCount, 5);
+        }
+
+        Assert.assertEquals(TestTarget.boostCount, 6);
+        Assert.assertEquals(TestTarget.unboostCount, 6);
+        Assert.assertEquals(TestTarget.invokeCount, 1);
+    }
+
+    @Test
+    public void testMethodWithControlFlow() {
+        TestTarget.resetCount();
+        TestTarget t = new TestTarget();
+
+        Assert.assertEquals(TestTarget.boostCount, 0);
+        Assert.assertEquals(TestTarget.unboostCount, 0);
+
+        if ((t.hashCode() + " ").contains("1")) {
+            t.synchronizedCall();
+        } else {
+            t.synchronizedCall();
+        }
+
+        // Should only be boosted once.
+        Assert.assertEquals(TestTarget.boostCount, 1);
+        Assert.assertEquals(TestTarget.unboostCount, 1);
+        Assert.assertEquals(TestTarget.invokeCount, 1);
+    }
+}
diff --git a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestTarget.java b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestTarget.java
new file mode 100644
index 0000000..8e7d478
--- /dev/null
+++ b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestTarget.java
@@ -0,0 +1,52 @@
+/*
+ * 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 lockedregioncodeinjection;
+
+public class TestTarget {
+  public static int boostCount = 0;
+  public static int unboostCount = 0;
+  public static int invokeCount = 0;
+
+  public static void boost() {
+    boostCount++;
+  }
+
+  public static void unboost() {
+    unboostCount++;
+  }
+
+  public static void invoke() {
+    invokeCount++;
+  }
+
+  public static void resetCount() {
+    boostCount = 0;
+    unboostCount = 0;
+    invokeCount = 0;
+  }
+
+  public synchronized void synchronizedCall() {
+    invoke();
+  }
+
+  public synchronized int synchronizedCallReturnInt() {
+    invoke();
+    return 0;
+  }
+
+  public synchronized Object synchronizedCallReturnObject() {
+    invoke();
+    return this;
+  }
+}