Merge "Increase timeout for testDeselectTrackForSubtitleTracks." into nyc-dev
diff --git a/OldCtsTestCaseList.mk b/OldCtsTestCaseList.mk
index 68130f3..aff9f3c 100644
--- a/OldCtsTestCaseList.mk
+++ b/OldCtsTestCaseList.mk
@@ -109,6 +109,8 @@
     CtsDeviceTaskSwitchingAppA \
     CtsDeviceTaskSwitchingAppB \
     CtsDeviceTaskSwitchingControl \
+    CtsDragAndDropSourceApp \
+    CtsDragAndDropTargetApp \
     CtsExternalServiceService \
     CtsHostsideNetworkTestsApp \
     CtsHostsideNetworkTestsApp2 \
@@ -266,6 +268,7 @@
     CtsAtraceHostTestCases \
     CtsCppToolsTestCases \
     CtsDevicePolicyManagerTestCases \
+    CtsDragAndDropHostTestCases \
     CtsDumpsysHostTestCases \
     CtsHostsideNetworkTests \
     CtsJdwpSecurityHostTestCases \
diff --git a/apps/CameraITS/pymodules/its/device.py b/apps/CameraITS/pymodules/its/device.py
index 4777f97..54bd0ad 100644
--- a/apps/CameraITS/pymodules/its/device.py
+++ b/apps/CameraITS/pymodules/its/device.py
@@ -58,6 +58,10 @@
 
     # Seconds timeout on each socket operation.
     SOCK_TIMEOUT = 10.0
+    # Additional timeout in seconds when ITS service is doing more complicated
+    # operations, for example: issuing warmup requests before actual capture.
+    EXTRA_SOCK_TIMEOUT = 5.0
+
     SEC_TO_NSEC = 1000*1000*1000.0
 
     PACKAGE = 'com.android.cts.verifier.camera.its'
@@ -327,9 +331,12 @@
         cmd = {}
         cmd["cmdName"] = "getSensorEvents"
         self.sock.send(json.dumps(cmd) + "\n")
+        timeout = self.SOCK_TIMEOUT + self.EXTRA_SOCK_TIMEOUT
+        self.sock.settimeout(timeout)
         data,_ = self.__read_response_from_socket()
         if data['tag'] != 'sensorEvents':
             raise its.error.Error('Invalid command response')
+        self.sock.settimeout(self.SOCK_TIMEOUT)
         return data['objValue']
 
     def get_camera_ids(self):
@@ -674,6 +681,8 @@
 
         extended_timeout = longest_exp_time / self.SEC_TO_NSEC + \
                 self.SOCK_TIMEOUT
+        if repeat_request:
+            extended_timeout += self.EXTRA_SOCK_TIMEOUT
         self.sock.settimeout(extended_timeout)
 
         print "Capturing %d frame%s with %d format%s [%s]" % (
diff --git a/apps/CameraITS/tests/scene1/test_locked_burst.py b/apps/CameraITS/tests/scene1/test_locked_burst.py
index 6552c73..daefb6b 100644
--- a/apps/CameraITS/tests/scene1/test_locked_burst.py
+++ b/apps/CameraITS/tests/scene1/test_locked_burst.py
@@ -32,7 +32,7 @@
     NAME = os.path.basename(__file__).split(".")[0]
 
     BURST_LEN = 8
-    SPREAD_THRESH_MANUAL_SENSOR = 0.005
+    SPREAD_THRESH_MANUAL_SENSOR = 0.01
     SPREAD_THRESH = 0.03
     FPS_MAX_DIFF = 2.0
 
diff --git a/apps/CameraITS/tests/scene1/test_post_raw_sensitivity_boost.py b/apps/CameraITS/tests/scene1/test_post_raw_sensitivity_boost.py
index 20558d7..61b431c 100644
--- a/apps/CameraITS/tests/scene1/test_post_raw_sensitivity_boost.py
+++ b/apps/CameraITS/tests/scene1/test_post_raw_sensitivity_boost.py
@@ -30,7 +30,10 @@
     NAME = os.path.basename(__file__).split(".")[0]
 
     # Each raw image
-    RATIO_THRESHOLD = 0.05
+    RATIO_THRESHOLD = 0.1
+    # Waive the check if raw pixel value is below this level (signal too small
+    # that small black level error converts to huge error in percentage)
+    RAW_PIXEL_VAL_THRESHOLD = 0.03
 
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
@@ -135,6 +138,8 @@
                         raw_rgb_means[step - 1][rgb],
                         raw_rgb_means[step][rgb],
                         ratio, raw_thres_min, raw_thres_max)
+                if (raw_rgb_means[step][rgb] <= RAW_PIXEL_VAL_THRESHOLD):
+                    continue
                 assert(raw_thres_min < ratio < raw_thres_max)
 
         # Test that each yuv step is about the same bright as their mean
@@ -142,9 +147,12 @@
         yuv_thres_max = 1 + RATIO_THRESHOLD
         for rgb in range(3):
             vals = [val[rgb] for val in yuv_rgb_means]
+            for step in range(len(reqs)):
+                if (raw_rgb_means[step][rgb] <= RAW_PIXEL_VAL_THRESHOLD):
+                    vals = vals[:step]
             mean = sum(vals) / len(vals)
             print "%s channel vals %s mean %f"%(rgb_str[rgb], vals, mean)
-            for step in range(len(reqs)):
+            for step in range(len(vals)):
                 ratio = vals[step] / mean
                 assert(yuv_thres_min < ratio < yuv_thres_max)
 
diff --git a/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py b/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
index e6fe4c2..c4f9b84 100644
--- a/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
+++ b/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
@@ -15,6 +15,7 @@
 import its.image
 import its.device
 import its.objects
+import its.caps
 import time
 import math
 import pylab
@@ -58,6 +59,11 @@
 THRESH_MAX_SHIFT_MS = 2
 THRESH_MIN_ROT = 0.001
 
+# lens facing
+FACING_FRONT = 0
+FACING_BACK = 1
+FACING_EXTERNAL = 2
+
 def main():
     """Test if image and motion sensor events are well synchronized.
 
@@ -98,7 +104,7 @@
 
     # Compute the camera rotation displacements (rad) between each pair of
     # adjacent frames.
-    cam_rots = get_cam_rotations(frames)
+    cam_rots = get_cam_rotations(frames, events["facing"])
     if max(abs(cam_rots)) < THRESH_MIN_ROT:
         print "Device wasn't moved enough"
         assert(0)
@@ -240,7 +246,7 @@
     gyro_rots = numpy.array(gyro_rots)
     return gyro_rots
 
-def get_cam_rotations(frames):
+def get_cam_rotations(frames, facing):
     """Get the rotations of the camera between each pair of frames.
 
     Takes N frames and returns N-1 angular displacements corresponding to the
@@ -264,8 +270,13 @@
         p1,st,_ = cv2.calcOpticalFlowPyrLK(gframe0, gframe1, p0, None,
                 **LK_PARAMS)
         tform = procrustes_rotation(p0[st==1], p1[st==1])
-        # TODO: Choose the sign for the rotation so the cam matches the gyro
-        rot = -math.atan2(tform[0, 1], tform[0, 0])
+        if facing == FACING_BACK:
+            rot = -math.atan2(tform[0, 1], tform[0, 0])
+        elif facing == FACING_FRONT:
+            rot = math.atan2(tform[0, 1], tform[0, 0])
+        else:
+            print "Unknown lens facing", facing
+            assert(0)
         rots.append(rot)
         if i == 1:
             # Save a debug visualization of the features that are being
@@ -323,16 +334,25 @@
         frames: List of RGB images as numpy arrays.
     """
     with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.sensor_fusion(props) and
+                             its.caps.manual_sensor(props) and
+                             props['android.lens.facing'] != FACING_EXTERNAL)
+
         print "Starting sensor event collection"
         cam.start_sensor_events()
 
         # Sleep a few seconds for gyro events to stabilize.
-        time.sleep(5)
+        time.sleep(2)
 
         # TODO: Ensure that OIS is disabled; set to DISABLE and wait some time.
 
         # Capture the frames.
-        props = cam.get_camera_properties()
+        facing = props['android.lens.facing']
+        if facing != FACING_FRONT and facing != FACING_BACK:
+            print "Unknown lens facing", facing
+            assert(0)
+
         fmt = {"format":"yuv", "width":W, "height":H}
         s,e,_,_,_ = cam.do_3a(get_results=True)
         req = its.objects.manual_capture_request(s, e)
@@ -350,7 +370,8 @@
         exptimes = [c["metadata"]["android.sensor.exposureTime"] for c in caps]
         readouts = [c["metadata"]["android.sensor.rollingShutterSkew"]
                     for c in caps]
-        events = {"gyro": gyro, "cam": zip(starts,exptimes,readouts)}
+        events = {"gyro": gyro, "cam": zip(starts,exptimes,readouts),
+                  "facing": facing}
         with open("%s_events.txt"%(NAME), "w") as f:
             f.write(json.dumps(events))
 
diff --git a/apps/CameraITS/tools/run_all_tests.py b/apps/CameraITS/tools/run_all_tests.py
index 7239c9a..52780eb 100644
--- a/apps/CameraITS/tools/run_all_tests.py
+++ b/apps/CameraITS/tools/run_all_tests.py
@@ -45,12 +45,15 @@
         "scene2":[],
         "scene3":[],
         "scene4":[],
-        "scene5":[]
+        "scene5":[],
+        "sensor_fusion":[]
     }
 
     # Get all the scene0 and scene1 tests, which can be run using the same
     # physical setup.
-    scenes = ["scene0", "scene1", "scene2", "scene3", "scene4", "scene5"]
+    scenes = ["scene0", "scene1", "scene2", "scene3", "scene4", "scene5",
+              "sensor_fusion"]
+
     scene_req = {
         "scene0" : None,
         "scene1" : "A grey card covering at least the middle 30% of the scene",
@@ -60,7 +63,14 @@
                    "middle 50% of the scene. See CameraITS.pdf section 2.3.4 "
                    "for more details",
         "scene5" : "Capture images with a diffuser attached to the camera. See "
-                   "CameraITS.pdf section 2.3.4 for more details"
+                   "CameraITS.pdf section 2.3.4 for more details",
+        "sensor_fusion" : "Rotating checkboard pattern. See "
+                          "sensor_fusion/SensorFusion.pdf for detailed "
+                          "instructions. Note that this test will be skipped "
+                          "on devices not supporting REALTIME camera timestamp."
+                          "If that is the case, no scene setup is required and "
+                          "you can just answer Y when being asked if the scene "
+                          "is okay"
     }
     scene_extra_args = {
         "scene5" : ["doAF=False"]
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 73557c8..603a80b 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -979,7 +979,7 @@
         \n\n4. Run the full ITS test suite on all possible camera Ids.
         (cd CameraITS; python tools/run_all_tests.py).  Once all
         of the tests have been run, the \'PASS\' button will be enabled if all of the tests have
-        succeeded.  Please note that these tests can take 20+ minutes to run.
+        succeeded. Please note that these tests can take more than 2 hours to run on some devices.
     </string>
     <string name="no_camera_manager">
         No camera manager exists!  This test device is in a bad state.
@@ -1814,8 +1814,8 @@
         Navigate to Device administrators and confirm that:\n
         \n
         - Device administrators outside of the work profile (if any) appear in the list, and the managed profile administrators are badged correctly.\n
-        - \"CTS Verifier\" exists under the Work category, and is activated.\n
-        - Attempting to uncheck badged \"CTS Verifier\" shows an alert dialog informing you that this admin can be deactivated only if you remove the managed profile.\n
+        - A badged \"CTS Verifier\" exists, and is activated.\n
+        - Attempting to uncheck the badged \"CTS Verifier\" shows a page allowing the user to remove the managed profile.\n
         \n
         Use the Back button to return to this page.
     </string>
@@ -1850,19 +1850,6 @@
         Then use the Back button to return to this test and mark accordingly.
     </string>
 
-    <string name="provisioning_byod_battery_settings">Profile-aware battery settings</string>
-    <string name="provisioning_byod_battery_settings_instruction">
-        Please press the Go button to open Battery page in settings.\n
-        \n
-        Verify that Battery page shows both badged and unbadged apps in the usage list.\n
-        \n
-        Note that the usage list only displays usage since last charge,
-        so you may need to unplug your device and use a badged and unbadged app
-        for a little while before they will appear in the list.\n
-        \n
-        Then use the Back button to return to this test and mark accordingly.
-    </string>
-
     <string name="provisioning_byod_wifi_data_usage_settings">Profile-aware data usage settings (Wi-Fi)</string>
     <string name="provisioning_byod_wifi_data_usage_settings_instruction">
         Please press the Go button to open the Settings page.\n
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java
index d528604..793c332 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java
@@ -97,6 +97,9 @@
     private static final int TIMEOUT_CALLBACK = 10;
     private static final int TIMEOUT_3A = 10;
 
+    // Timeout for doCapture command polling
+    private static final int TIMEOUT_POLL_MS = 500;
+
     // Time given for background requests to warm up pipeline
     private static final long PIPELINE_WARMUP_TIME_MS = 2000;
 
@@ -564,6 +567,7 @@
             // Each command is a serialized JSON object.
             try {
                 JSONObject cmdObj = new JSONObject(cmd);
+                Logt.i(TAG, "Start processing command" + cmdObj.getString("cmdName"));
                 if ("open".equals(cmdObj.getString("cmdName"))) {
                     int cameraId = cmdObj.getInt("cameraId");
                     openCameraDevice(cameraId);
@@ -588,6 +592,7 @@
                 } else {
                     throw new ItsException("Unknown command: " + cmd);
                 }
+                Logt.i(TAG, "Finish processing command" + cmdObj.getString("cmdName"));
             } catch (org.json.JSONException e) {
                 Logt.e(TAG, "Invalid command: ", e);
             }
@@ -642,6 +647,7 @@
 
         public void sendResponse(LinkedList<MySensorEvent> events)
                 throws ItsException {
+            Logt.i(TAG, "Sending " + events.size() + " sensor events");
             try {
                 JSONArray accels = new JSONArray();
                 JSONArray mags = new JSONArray();
@@ -668,6 +674,7 @@
             } catch (org.json.JSONException e) {
                 throw new ItsException("JSON error: ", e);
             }
+            Logt.i(TAG, "Sent sensor events");
         }
 
         public void sendResponse(CameraCharacteristics props)
@@ -1273,16 +1280,20 @@
             // Make sure all callbacks have been hit (wait until captures are done).
             // If no timeouts are received after a timeout, then fail.
             int currentCount = mCountCallbacksRemaining.get();
+            long totalSleep = 0;
             while (currentCount > 0) {
                 try {
-                    Thread.sleep(timeout);
+                    Thread.sleep(TIMEOUT_POLL_MS);
                 } catch (InterruptedException e) {
                     throw new ItsException("Timeout failure", e);
                 }
+                totalSleep += TIMEOUT_POLL_MS;
                 int newCount = mCountCallbacksRemaining.get();
-                if (newCount == currentCount) {
+                if (newCount == currentCount && totalSleep > timeout) {
                     throw new ItsException(
                             "No callback received within timeout");
+                } else if (newCount < currentCount) {
+                    totalSleep = 0;
                 }
                 currentCount = newCount;
             }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodFlowTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodFlowTestActivity.java
index 5a20936..6aacea8 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodFlowTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodFlowTestActivity.java
@@ -71,7 +71,6 @@
     private DialogTestListItem mWorkStatusBarToastTest;
     private DialogTestListItem mAppSettingsVisibleTest;
     private DialogTestListItem mLocationSettingsVisibleTest;
-    private DialogTestListItem mBatterySettingsVisibleTest;
     private DialogTestListItem mWiFiDataUsageSettingsVisibleTest;
     private DialogTestListItem mCellularDataUsageSettingsVisibleTest;
     private DialogTestListItem mCredSettingsVisibleTest;
@@ -266,12 +265,6 @@
                 R.string.provisioning_byod_location_settings_instruction,
                 new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));
 
-        mBatterySettingsVisibleTest = new DialogTestListItem(this,
-                R.string.provisioning_byod_battery_settings,
-                "BYOD_BatterySettingsVisibleTest",
-                R.string.provisioning_byod_battery_settings_instruction,
-                new Intent(Intent.ACTION_POWER_USAGE_SUMMARY));
-
         mWiFiDataUsageSettingsVisibleTest = new DialogTestListItem(this,
                 R.string.provisioning_byod_wifi_data_usage_settings,
                 "BYOD_WiFiDataUsageSettingsVisibleTest",
@@ -406,7 +399,6 @@
         adapter.add(mCredSettingsVisibleTest);
         adapter.add(mAppSettingsVisibleTest);
         adapter.add(mLocationSettingsVisibleTest);
-        adapter.add(mBatterySettingsVisibleTest);
         adapter.add(mWiFiDataUsageSettingsVisibleTest);
         adapter.add(mCellularDataUsageSettingsVisibleTest);
         adapter.add(mPrintSettingsVisibleTest);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/RVCVCameraPreview.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/RVCVCameraPreview.java
index fa89b71..28d5d1c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/RVCVCameraPreview.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/RVCVCameraPreview.java
@@ -26,6 +26,7 @@
 import android.view.ViewGroup;
 
 import java.io.IOException;
+import java.lang.Math;
 
 /** Camera preview class */
 public class RVCVCameraPreview extends SurfaceView implements SurfaceHolder.Callback {
@@ -94,10 +95,10 @@
             ViewGroup.LayoutParams layout = getLayoutParams();
             if ( (float)v_height/v_width  >
                     mAspect) {
-                layout.height = (int)(v_width * mAspect);
+                layout.height = (int)Math.round(v_width * mAspect);
                 layout.width = v_width;
             }else {
-                layout.width = (int)(v_height / mAspect);
+                layout.width = (int)Math.round(v_height / mAspect);
                 layout.height = v_height;
             }
             Log.d(TAG, String.format("Layout (%d, %d) -> (%d, %d)", v_width, v_height,
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/RVCVRecordActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/RVCVRecordActivity.java
index 1ef87bd..21afe0d 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/RVCVRecordActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/RVCVRecordActivity.java
@@ -451,6 +451,7 @@
                 }
             }
             if (profile != null) {
+                param = mCamera.getParameters(); //acquire proper fov after change the picture size
                 float fovW = param.getHorizontalViewAngle();
                 float fovH = param.getVerticalViewAngle();
                 writeVideoMetaInfo(profile.videoFrameWidth, profile.videoFrameHeight,
diff --git a/build/test_host_java_library.mk b/build/test_host_java_library.mk
index 7fdefb5..abf6829 100644
--- a/build/test_host_java_library.mk
+++ b/build/test_host_java_library.mk
@@ -17,6 +17,8 @@
 # package XML needed by CTS.
 #
 
+LOCAL_STATIC_JAVA_LIBRARIES += platform-test-annotations-host
+
 include $(BUILD_HOST_JAVA_LIBRARY)
 include $(BUILD_CTS_MODULE_TEST_CONFIG)
 
diff --git a/build/test_package.mk b/build/test_package.mk
index c6b0865..690e2e9 100644
--- a/build/test_package.mk
+++ b/build/test_package.mk
@@ -22,6 +22,7 @@
 # Disable by default so "m cts" will work in emulator builds
 LOCAL_DEX_PREOPT := false
 LOCAL_PROGUARD_ENABLED := disabled
+LOCAL_STATIC_JAVA_LIBRARIES += platform-test-annotations
 
 include $(BUILD_CTS_SUPPORT_PACKAGE)
 include $(BUILD_CTS_MODULE_TEST_CONFIG)
diff --git a/build/test_target_java_library.mk b/build/test_target_java_library.mk
index 525abb5..c2a93d4 100644
--- a/build/test_target_java_library.mk
+++ b/build/test_target_java_library.mk
@@ -18,6 +18,7 @@
 #
 # Disable by default so "m cts" will work in emulator builds
 LOCAL_DEX_PREOPT := false
+LOCAL_STATIC_JAVA_LIBRARIES += platform-test-annotations
 include $(BUILD_JAVA_LIBRARY)
 include $(BUILD_CTS_MODULE_TEST_CONFIG)
 
diff --git a/build/test_target_testng_package.mk b/build/test_target_testng_package.mk
index 0621109..d038917 100644
--- a/build/test_target_testng_package.mk
+++ b/build/test_target_testng_package.mk
@@ -18,6 +18,7 @@
 #
 # Disable by default so "m cts" will work in emulator builds
 LOCAL_DEX_PREOPT := false
+LOCAL_STATIC_JAVA_LIBRARIES += platform-test-annotations
 include $(BUILD_JAVA_LIBRARY)
 include $(BUILD_CTS_MODULE_TEST_CONFIG)
 
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/DeviceReportLog.java b/common/device-side/util/src/com/android/compatibility/common/util/DeviceReportLog.java
index 204466a..8bdd0bd 100644
--- a/common/device-side/util/src/com/android/compatibility/common/util/DeviceReportLog.java
+++ b/common/device-side/util/src/com/android/compatibility/common/util/DeviceReportLog.java
@@ -279,4 +279,18 @@
             instrumentation.sendStatus(INST_STATUS_ERROR, null);
         }
     }
+
+    /**
+     * Closes report file. Static functions that do not have access to instrumentation can
+     * use this to close report logs. Summary, if present, is not reported to instrumentation, hence
+     * does not appear in the result XML.
+     */
+    public void submit() {
+        Log.i(TAG, "Submit");
+        try {
+            store.close();
+        } catch (IOException e) {
+            Log.e(TAG, "Submit Failed", e);
+        }
+    }
 }
diff --git a/common/host-side/tradefed/res/config/common-compatibility-config.xml b/common/host-side/tradefed/res/config/common-compatibility-config.xml
index 7ffbea5..0006203 100644
--- a/common/host-side/tradefed/res/config/common-compatibility-config.xml
+++ b/common/host-side/tradefed/res/config/common-compatibility-config.xml
@@ -17,6 +17,8 @@
     <device_recovery class="com.android.tradefed.device.WaitDeviceRecovery" />
     <build_provider class="com.android.compatibility.common.tradefed.build.CompatibilityBuildProvider" />
     <test class="com.android.compatibility.common.tradefed.testtype.CompatibilityTest" />
+    <option name="compatibility:test-arg" value="com.android.tradefed.testtype.AndroidJUnitTest:rerun-from-file:true" />
+    <option name="compatibility:test-arg" value="com.android.tradefed.testtype.AndroidJUnitTest:fallback-to-serial-rerun:false" />
     <logger class="com.android.tradefed.log.FileLogger">
         <option name="log-level-display" value="WARN" />
     </logger>
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelper.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelper.java
index e21f98e..522d372 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelper.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelper.java
@@ -21,6 +21,8 @@
 
 import java.io.File;
 import java.io.FileNotFoundException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -37,8 +39,11 @@
     private static final String SUITE_VERSION = "SUITE_VERSION";
     private static final String SUITE_PLAN = "SUITE_PLAN";
     private static final String RESULT_DIR = "RESULT_DIR";
+    private static final String START_TIME_MS = "START_TIME_MS";
     private static final String CONFIG_PATH_PREFIX = "DYNAMIC_CONFIG_FILE:";
     private static final String DYNAMIC_CONFIG_OVERRIDE_URL = "DYNAMIC_CONFIG_OVERRIDE_URL";
+    private static final String COMMAND_LINE_ARGS = "command_line_args";
+    private static final String RETRY_COMMAND_LINE_ARGS = "retry_command_line_args";
     private final IBuildInfo mBuildInfo;
     private boolean mInitialized = false;
 
@@ -50,9 +55,17 @@
     }
 
     /**
-     * Initializes the {@link IBuildInfo} from the manifest.
+     * Initializes the {@link IBuildInfo} from the manifest with the current time
+     * as the start time.
      */
     public void init(String suitePlan, String dynamicConfigUrl) {
+        init(suitePlan, dynamicConfigUrl, System.currentTimeMillis());
+    }
+
+    /**
+     * Initializes the {@link IBuildInfo} from the manifest.
+     */
+    public void init(String suitePlan, String dynamicConfigUrl, long startTimeMs) {
         if (mInitialized) {
             return;
         }
@@ -62,6 +75,8 @@
         mBuildInfo.addBuildAttribute(SUITE_FULL_NAME, SuiteInfo.FULLNAME);
         mBuildInfo.addBuildAttribute(SUITE_VERSION, SuiteInfo.VERSION);
         mBuildInfo.addBuildAttribute(SUITE_PLAN, suitePlan);
+        mBuildInfo.addBuildAttribute(START_TIME_MS, Long.toString(startTimeMs));
+        mBuildInfo.addBuildAttribute(RESULT_DIR, getDirSuffix(startTimeMs));
         String rootDirPath = null;
         if (mBuildInfo instanceof IFolderBuildInfo) {
             File rootDir = ((IFolderBuildInfo) mBuildInfo).getRootDir();
@@ -86,6 +101,24 @@
         }
     }
 
+    public IBuildInfo getBuildInfo() {
+        return mBuildInfo;
+    }
+
+    public void setRetryCommandLineArgs(String commandLineArgs) {
+        mBuildInfo.addBuildAttribute(RETRY_COMMAND_LINE_ARGS, commandLineArgs);
+    }
+
+    public String getCommandLineArgs() {
+        if (mBuildInfo.getBuildAttributes().containsKey(RETRY_COMMAND_LINE_ARGS)) {
+            return mBuildInfo.getBuildAttributes().get(RETRY_COMMAND_LINE_ARGS);
+        } else {
+            // NOTE: this is a temporary workaround set in TestInvocation#invoke in tradefed.
+            // This will be moved to a separate method in a new invocation metadata class.
+            return mBuildInfo.getBuildAttributes().get(COMMAND_LINE_ARGS);
+        }
+    }
+
     public String getSuiteBuild() {
         return mBuildInfo.getBuildAttributes().get(SUITE_BUILD);
     }
@@ -110,6 +143,10 @@
         return mBuildInfo.getBuildAttributes().get(DYNAMIC_CONFIG_OVERRIDE_URL);
     }
 
+    public long getStartTime() {
+        return Long.parseLong(mBuildInfo.getBuildAttributes().get(START_TIME_MS));
+    }
+
     public void addDynamicConfigFile(String moduleName, File configFile) {
         mBuildInfo.addBuildAttribute(CONFIG_PATH_PREFIX + moduleName, configFile.getAbsolutePath());
     }
@@ -172,18 +209,12 @@
     }
 
     /**
-     * Sets the name of the current invocation's result directory.
-     */
-    public void setResultDir(String resultDir) {
-        mBuildInfo.addBuildAttribute(RESULT_DIR, resultDir);
-    }
-
-    /**
      * @return a {@link File} representing the result directory of the current invocation.
      * @throws FileNotFoundException if the directory structure is not valid.
      */
     public File getResultDir() throws FileNotFoundException {
-        return new File(getResultsDir(), mBuildInfo.getBuildAttributes().get(RESULT_DIR));
+        return new File(getResultsDir(),
+            getDirSuffix(Long.parseLong(mBuildInfo.getBuildAttributes().get(START_TIME_MS))));
     }
 
     /**
@@ -208,4 +239,10 @@
         return testsDir;
     }
 
+    /**
+     * @return a {@link String} to use for directory suffixes created from the given time.
+     */
+    public static String getDirSuffix(long millis) {
+        return new SimpleDateFormat("yyyy.MM.dd_HH.mm.ss").format(new Date(millis));
+    }
 }
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/MasterBuildInfo.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/MasterBuildInfo.java
deleted file mode 100644
index fdcb634..0000000
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/build/MasterBuildInfo.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2016 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.compatibility.common.tradefed.build;
-
-import com.android.tradefed.build.IBuildInfo;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Singleton class containing a master {@link IBuildInfo} to be persisted across sharded
- * invocations.
- */
-public class MasterBuildInfo {
-
-    private static Map<String, String> mBuild = new HashMap<String, String>();
-
-    private MasterBuildInfo() { }
-
-    public static Map<String, String> getBuild() {
-        return mBuild;
-    }
-
-    /**
-     *
-     * @param buildInfo
-     */
-    public static void addBuildInfo(Map<String, String> buildInfo) {
-        mBuild.putAll(buildInfo);
-    }
-
-    public static void clear() {
-        mBuild.clear();
-    }
-}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/command/CompatibilityConsole.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/command/CompatibilityConsole.java
index e79659b..9daf9e4 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/command/CompatibilityConsole.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/command/CompatibilityConsole.java
@@ -65,7 +65,6 @@
         MODULE_SPLIT_EXCLUSIONS.add("CtsDeqpTestCases");
     }
     private CompatibilityBuildHelper mBuildHelper;
-    private static final int TIMESTAMP_COLUMN = 4;
 
     /**
      * {@inheritDoc}
@@ -171,7 +170,7 @@
         helpBuilder.append("Options:\n");
         helpBuilder.append("  --serial/-s <device_id>: The device to run the test on.\n");
         helpBuilder.append("  --abi/-a <abi>: The ABI to run the test against.\n");
-        helpBuilder.append("  --shard <shards>: Shards a run into the given number of independant");
+        helpBuilder.append("  --shards <shards>: Shards a run into the given number of independant");
         helpBuilder.append(" chunks, to run on multiple devices in parallel.\n");
         helpBuilder.append("  --logcat-on-failure: Capture logcat when a test fails.\n");
         helpBuilder.append("  --bugreport-on-failure: Capture a bugreport when a test fails.\n");
@@ -323,13 +322,13 @@
         if (testResultRepo != null && results.size() > 0) {
             for (int i = 0; i < results.size(); i++) {
                 IInvocationResult result = results.get(i);
-                Map<String, String> buildInfo = result.getBuildInfo();
+                Map<String, String> invocationInfo = result.getInvocationInfo();
 
-                // build attributes are not always present (e.g. in the case of halted test runs)
+                // invocation attributes are not always present (e.g. in the case of halted runs)
                 // replace null entries with the string "Unknown"
-                for (Map.Entry<String, String> entry : buildInfo.entrySet()) {
+                for (Map.Entry<String, String> entry : invocationInfo.entrySet()) {
                     if (entry.getValue() == null) {
-                        buildInfo.put(entry.getKey(), "Unknown");
+                        invocationInfo.put(entry.getKey(), "Unknown");
                     }
                 }
 
@@ -338,24 +337,17 @@
                         Integer.toString(result.countResults(TestStatus.PASS)),
                         Integer.toString(result.countResults(TestStatus.FAIL)),
                         Integer.toString(result.countResults(TestStatus.NOT_EXECUTED)),
-                        TimeUtil.formatTimeStamp(result.getStartTime()),
+                        CompatibilityBuildHelper.getDirSuffix(result.getStartTime()),
                         result.getTestPlan(),
                         ArrayUtil.join(", ", result.getDeviceSerials()),
-                        buildInfo.get("build_id"),
-                        buildInfo.get("build_product")
+                        invocationInfo.get("build_id"),
+                        invocationInfo.get("build_product")
                         ));
-
-                // sort the table entries on each entry's formatted timestamp
-                Collections.sort(table, new Comparator<List<String>>() {
-                    public int compare(List<String> firstList, List<String> secondList) {
-                        return firstList.get(TIMESTAMP_COLUMN)
-                                .compareTo(secondList.get(TIMESTAMP_COLUMN));
-                    }
-                });
             }
 
+
             // add the table header to the beginning of the list
-            table.add(0, Arrays.asList("Session", "Pass", "Fail", "Not Executed", "Start Time",
+            table.add(0, Arrays.asList("Session", "Pass", "Fail", "Not Executed", "Result Directory",
                     "Test Plan", "Device serial(s)", "Build ID", "Product"));
             tableFormatter.displayTable(table, new PrintWriter(System.out, true));
         } else {
@@ -367,7 +359,8 @@
         if (mBuildHelper == null) {
             CompatibilityBuildProvider buildProvider = new CompatibilityBuildProvider();
             mBuildHelper = new CompatibilityBuildHelper(buildProvider.getBuild());
-            mBuildHelper.init("" /* suite plan */, "" /* dynamic config url */);
+            mBuildHelper.init(
+                "" /* suite plan */, "" /* dynamic config url */, -1 /*startTimeMs*/);
         }
         return mBuildHelper;
     }
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ConsoleReporter.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ConsoleReporter.java
index 19adb3f..8728f4f 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ConsoleReporter.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ConsoleReporter.java
@@ -43,6 +43,10 @@
     private String mDeviceSerial = UNKNOWN_DEVICE;
     private boolean mTestFailed;
     private String mModuleId;
+    private int mCurrentTestNum;
+    private int mTotalTestsInModule;
+    private int mPassedTests;
+    private int mFailedTests;
 
     /**
      * {@inheritDoc}
@@ -53,7 +57,7 @@
             CLog.w("buildInfo should not be null");
             return;
         }
-
+        // Escape any "%" signs in the device serial.
         mDeviceSerial = buildInfo.getDeviceSerial().replace("%", "%%");
     }
 
@@ -62,8 +66,21 @@
      */
     @Override
     public void testRunStarted(String id, int numTests) {
-        mModuleId = id;
-        log("Starting %s with %d test%s", id, numTests, (numTests > 1) ? "s" : "");
+        if (mModuleId == null || !mModuleId.equals(id)) {
+            mModuleId = id;
+            mTotalTestsInModule = numTests;
+            // Reset counters
+            mCurrentTestNum = 0;
+            mPassedTests = 0;
+            mFailedTests = 0;
+            mTestFailed = false;
+            logMessage("Starting %s with %d test%s",
+                    id, mTotalTestsInModule, (mTotalTestsInModule > 1) ? "s" : "");
+        } else {
+            mTotalTestsInModule += numTests;
+            logMessage("Continuing %s with %d test%s",
+                    id, mTotalTestsInModule, (mTotalTestsInModule > 1) ? "s" : "");
+        }
     }
 
     /**
@@ -72,6 +89,7 @@
     @Override
     public void testStarted(TestIdentifier test) {
         mTestFailed = false;
+        mCurrentTestNum++;
     }
 
     /**
@@ -79,8 +97,9 @@
      */
     @Override
     public void testFailed(TestIdentifier test, String trace) {
-        log("%s fail: %s", test, trace);
+        logProgress("%s fail: %s", test, trace);
         mTestFailed = true;
+        mFailedTests++;
     }
 
     /**
@@ -88,7 +107,7 @@
      */
     @Override
     public void testIgnored(TestIdentifier test) {
-        log("%s ignore", test);
+        logProgress("%s ignore", test);
     }
 
     /**
@@ -96,7 +115,7 @@
      */
     @Override
     public void testAssumptionFailure(TestIdentifier test, String trace) {
-        log("%s failed assumption: %s", test, trace);
+        logProgress("%s failed assumption: %s", test, trace);
     }
 
     /**
@@ -105,7 +124,8 @@
     @Override
     public void testEnded(TestIdentifier test, Map<String, String> testMetrics) {
         if (!mTestFailed) {
-            log("%s pass", test);
+            logProgress("%s pass", test);
+            mPassedTests++;
         }
     }
 
@@ -114,7 +134,24 @@
      */
     @Override
     public void testRunFailed(String errorMessage) {
-        log(errorMessage);
+        logMessage(errorMessage);
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testRunEnded(long elapsedTime, Map<String, String> metrics) {
+        int notExecuted = mTotalTestsInModule - mCurrentTestNum;
+        String status = notExecuted > 0 ? "failed" : "completed";
+        logMessage("%s %s in %s. %d passed, %d failed, %d not executed",
+            mModuleId,
+            status,
+            TimeUtil.formatElapsedTime(elapsedTime),
+            mPassedTests,
+            mFailedTests,
+            notExecuted);
     }
 
     /**
@@ -122,16 +159,29 @@
      */
     @Override
     public void testRunStopped(long elapsedTime) {
-        log("%s stopped (%s)", mModuleId, TimeUtil.formatElapsedTime(elapsedTime));
+        logMessage("%s stopped (%s)", mModuleId, TimeUtil.formatElapsedTime(elapsedTime));
+    }
+
+    /**
+     * Print out message with test execution status.
+     */
+    private void logProgress(String format, Object... args) {
+        format = String.format("[%s %s %s] %s", progress(), mModuleId, mDeviceSerial, format);
+        log(format, args);
+    }
+
+    /**
+     * Print out message to the console
+     */
+    private void logMessage(String format, Object... args) {
+        format = String.format("[%s] %s", mDeviceSerial, format);
+        log(format, args);
     }
 
     /**
      * Print out to the console or log silently when mQuietOutput is true.
      */
     private void log(String format, Object... args) {
-        // Escape any "%" signs in the device serial.
-        format = String.format("[%s] %s", mDeviceSerial, format);
-
         if (mQuietOutput) {
             CLog.i(format, args);
         } else {
@@ -148,4 +198,39 @@
         OptionCopier.copyOptionsNoThrow(this, clone);
         return clone;
     }
+
+    /**
+     * Return a string containing the percentage complete of module test execution.
+     */
+    private String progress() {
+        return String.format("%d/%d", mCurrentTestNum, mTotalTestsInModule);
+    }
+
+    String getDeviceSerial() {
+        return mDeviceSerial;
+    }
+
+    boolean getTestFailed() {
+        return mTestFailed;
+    }
+
+    String getModuleId() {
+        return mModuleId;
+    }
+
+    int getCurrentTestNum() {
+        return mCurrentTestNum;
+    }
+
+    int getTotalTestsInModule() {
+        return mTotalTestsInModule;
+    }
+
+    int getPassedTests() {
+        return mPassedTests;
+    }
+
+    int getFailedTests() {
+        return mFailedTests;
+    }
 }
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java
index ae74c26..b5a7929 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java
@@ -16,7 +16,6 @@
 package com.android.compatibility.common.tradefed.result;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.compatibility.common.tradefed.build.MasterBuildInfo;
 import com.android.compatibility.common.tradefed.testtype.CompatibilityTest;
 import com.android.compatibility.common.util.ICaseResult;
 import com.android.compatibility.common.util.IInvocationResult;
@@ -35,9 +34,11 @@
 import com.android.tradefed.config.Option;
 import com.android.tradefed.config.Option.Importance;
 import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.config.OptionCopier;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.result.ILogSaver;
 import com.android.tradefed.result.ILogSaverListener;
+import com.android.tradefed.result.IShardableListener;
 import com.android.tradefed.result.ITestInvocationListener;
 import com.android.tradefed.result.ITestSummaryListener;
 import com.android.tradefed.result.InputStreamSource;
@@ -59,17 +60,20 @@
 import java.io.InputStream;
 import java.text.SimpleDateFormat;
 import java.util.Date;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Set;
 
 /**
  * Collect test results for an entire invocation and output test results to disk.
  */
 @OptionClass(alias="result-reporter")
 public class ResultReporter implements ILogSaverListener, ITestInvocationListener,
-       ITestSummaryListener {
+       ITestSummaryListener, IShardableListener {
 
+    private static final String UNKNOWN_DEVICE = "unknown_device";
     private static final String RESULT_KEY = "COMPATIBILITY_TEST_RESULT";
     private static final String CTS_PREFIX = "cts:";
     private static final String BUILD_INFO = CTS_PREFIX + "build_";
@@ -97,100 +101,138 @@
     @Option(name = "use-log-saver", description = "Also saves generated result with log saver")
     private boolean mUseLogSaver = false;
 
-    private String mDeviceSerial;
-
-    private IInvocationResult mResult;
+    private CompatibilityBuildHelper mBuildHelper;
     private File mResultDir = null;
     private File mLogDir = null;
-    private long mStartTime;
     private ResultUploader mUploader;
     private String mReferenceUrl;
+    private ILogSaver mLogSaver;
+    private int invocationEndedCount = 0;
+
+    private IInvocationResult mResult = new InvocationResult();
     private IModuleResult mCurrentModuleResult;
     private ICaseResult mCurrentCaseResult;
     private ITestResult mCurrentResult;
-    private IBuildInfo mBuild;
-    private CompatibilityBuildHelper mBuildHelper;
-    private ILogSaver mLogSaver;
-    private boolean mReloadBuildInfo = false;
+    private String mDeviceSerial = UNKNOWN_DEVICE;
+    private Set<String> mMasterDeviceSerials = new HashSet<>();
+    private Set<IBuildInfo> mMasterBuildInfos = new HashSet<>();
+
+    // Nullable. If null, "this" is considered the master and must handle
+    // result aggregation and reporting. When not null, it should forward events
+    // to the master.
+    private final ResultReporter mMasterResultReporter;
+
+    /**
+     * Default constructor.
+     */
+    public ResultReporter() {
+        this(null);
+    }
+
+    /**
+     * Construct a shard ResultReporter that forwards module results to the
+     * masterResultReporter.
+     */
+    public ResultReporter(ResultReporter masterResultReporter) {
+        mMasterResultReporter = masterResultReporter;
+    }
 
     /**
      * {@inheritDoc}
      */
     @Override
     public void invocationStarted(IBuildInfo buildInfo) {
-        mBuild = buildInfo;
-        mBuildHelper = new CompatibilityBuildHelper(mBuild);
-        mDeviceSerial = buildInfo.getDeviceSerial();
-        if (mDeviceSerial == null) {
-            mDeviceSerial = "unknown_device";
-            mReloadBuildInfo = true;
+        synchronized(this) {
+            if (mBuildHelper == null) {
+                mBuildHelper = new CompatibilityBuildHelper(buildInfo);
+            }
+            if (mDeviceSerial == null && buildInfo.getDeviceSerial() != null) {
+                mDeviceSerial = buildInfo.getDeviceSerial();
+            }
         }
-        long time = System.currentTimeMillis();
-        String dirSuffix = getDirSuffix(time);
-        if (mRetrySessionId != null) {
-            CLog.d("[%s] Retrying session %d", mDeviceSerial, mRetrySessionId);
-            List<IInvocationResult> results = null;
-            try {
-                results = ResultHandler.getResults(mBuildHelper.getResultsDir());
-            } catch (FileNotFoundException e) {
-                e.printStackTrace();
-            }
-            if (results != null && mRetrySessionId >= 0 && mRetrySessionId < results.size()) {
-                mResult = results.get(mRetrySessionId);
-            } else {
-                throw new IllegalArgumentException(
-                        String.format("Could not find session %d",mRetrySessionId));
-            }
-            mStartTime = mResult.getStartTime();
-            mResultDir = mResult.getResultDir();
-        } else {
-            mStartTime = time;
-            try {
-                mResultDir = new File(mBuildHelper.getResultsDir(), dirSuffix);
-            } catch (FileNotFoundException e) {
-                e.printStackTrace();
-            }
-            if (mResultDir != null && mResultDir.mkdirs()) {
-                info("Created result dir %s", mResultDir.getAbsolutePath());
-            } else {
-                throw new IllegalArgumentException(String.format("Could not create result dir %s",
-                        mResultDir.getAbsolutePath()));
-            }
-            mResult = new InvocationResult(mStartTime, mResultDir);
+
+        if (isShardResultReporter()) {
+            // Shard ResultReporters forward invocationStarted to the mMasterResultReporter
+            mMasterResultReporter.invocationStarted(buildInfo);
+            return;
         }
-        mBuildHelper.setResultDir(mResultDir.getName());
+
+        // NOTE: Everything after this line only applies to the master ResultReporter.
+
+        synchronized(this) {
+            if (buildInfo.getDeviceSerial() != null) {
+                // The master ResultReporter collects all device serials being used
+                // for the current implementation.
+                mMasterDeviceSerials.add(buildInfo.getDeviceSerial());
+            }
+
+            // The master ResultReporter collects all buildInfos.
+            mMasterBuildInfos.add(buildInfo);
+
+            if (mResultDir == null) {
+                // For the non-sharding case, invocationStarted is only called once,
+                // but for the sharding case, this might be called multiple times.
+                // Logic used to initialize the result directory should not be
+                // invoked twice during the same invocation.
+                initializeResultDirectories();
+            }
+        }
+    }
+
+    /**
+     * Create directory structure where results and logs will be written.
+     */
+    private void initializeResultDirectories() {
+        info("Initializing result directory");
+
+        try {
+            // Initialize the result directory. Either a new directory or reusing
+            // an existing session.
+            if (mRetrySessionId != null) {
+                // Overwrite the mResult with the test results of the previous session
+                mResult = ResultHandler.findResult(mBuildHelper.getResultsDir(), mRetrySessionId);
+            }
+            mResult.setStartTime(mBuildHelper.getStartTime());
+            mResultDir = mBuildHelper.getResultDir();
+            if (mResultDir != null) {
+                mResultDir.mkdirs();
+            }
+        } catch (FileNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+
+        if (mResultDir == null) {
+            throw new RuntimeException("Result Directory was not created");
+        }
+        if (!mResultDir.exists()) {
+            throw new RuntimeException("Result Directory was not created: " +
+                    mResultDir.getAbsolutePath());
+        }
+
+        info("Results Directory: " + mResultDir.getAbsolutePath());
+
         mUploader = new ResultUploader(mResultServer, mBuildHelper.getSuiteName());
         try {
-            mLogDir = new File(mBuildHelper.getLogsDir(), dirSuffix);
+            mLogDir = new File(mBuildHelper.getLogsDir(),
+                    CompatibilityBuildHelper.getDirSuffix(mBuildHelper.getStartTime()));
         } catch (FileNotFoundException e) {
             e.printStackTrace();
         }
         if (mLogDir != null && mLogDir.mkdirs()) {
             info("Created log dir %s", mLogDir.getAbsolutePath());
-        } else {
+        }
+        if (mLogDir == null || !mLogDir.exists()) {
             throw new IllegalArgumentException(String.format("Could not create log dir %s",
                     mLogDir.getAbsolutePath()));
         }
     }
 
     /**
-     * @return a {@link String} to use for directory suffixes created from the given time.
-     */
-    private String getDirSuffix(long time) {
-        return new SimpleDateFormat("yyyy.MM.dd_HH.mm.ss").format(new Date(time));
-    }
-
-    /**
      * {@inheritDoc}
      */
     @Override
     public void testRunStarted(String id, int numTests) {
         mCurrentModuleResult = mResult.getOrCreateModule(id);
-        if (mDeviceSerial == null || mDeviceSerial.equals("unknown_device")) {
-            mResult.addDeviceSerial(mBuild.getDeviceSerial());
-        } else {
-            mResult.addDeviceSerial(mDeviceSerial);
-        }
     }
 
     /**
@@ -270,20 +312,25 @@
      */
     @Override
     public void testRunEnded(long elapsedTime, Map<String, String> metrics) {
-        // Get device info from build attributes
-        for (Entry<String, String> entry : mBuild.getBuildAttributes().entrySet()) {
-            String key = entry.getKey();
-            if (key.startsWith(BUILD_INFO)) {
-                mResult.addBuildInfo(key.substring(CTS_PREFIX.length()), entry.getValue());
-            }
-        }
         mCurrentModuleResult.addRuntime(elapsedTime);
-        info("%s completed in %s. %d passed, %d failed, %d not executed",
-                mCurrentModuleResult.getId(),
-                TimeUtil.formatElapsedTime(elapsedTime),
-                mCurrentModuleResult.countResults(TestStatus.PASS),
-                mCurrentModuleResult.countResults(TestStatus.FAIL),
-                mCurrentModuleResult.countResults(TestStatus.NOT_EXECUTED));
+
+        if (isShardResultReporter()) {
+            // Forward module results to the master.
+            mMasterResultReporter.mergeModuleResult(mCurrentModuleResult);
+        }
+    }
+
+    /**
+     * Directly add a module result. Note: this method is meant to be used by
+     * a shard ResultReporter.
+     */
+    private void mergeModuleResult(IModuleResult moduleResult) {
+        // This merges the results in moduleResult to any existing results already
+        // contained in mResult. This is useful for retries and allows the final
+        // report from a retry to contain all test results.
+        synchronized(this) {
+            mResult.mergeModuleResult(moduleResult);
+        }
     }
 
     /**
@@ -308,6 +355,8 @@
      */
     @Override
     public void putSummary(List<TestSummary> summaries) {
+        // This is safe to be invoked on either the master or a shard ResultReporter,
+        // but the value added to the report will be that of the master ResultReporter.
         if (summaries.size() > 0) {
             mReferenceUrl = summaries.get(0).getSummary().getString();
         }
@@ -318,65 +367,68 @@
      */
     @Override
     public void invocationEnded(long elapsedTime) {
+        if (isShardResultReporter()) {
+            // Shard ResultReporters report
+            mMasterResultReporter.invocationEnded(elapsedTime);
+            return;
+        }
+
+        // NOTE: Everything after this line only applies to the master ResultReporter.
+
+
+        synchronized(this) {
+            // The master ResultReporter tracks the progress of all invocations across
+            // shard ResultReporters. Writing results should not proceed until all
+            // ResultReporters have completed.
+            if (++invocationEndedCount < mMasterBuildInfos.size()) {
+                return;
+            }
+            finalizeResultDirectories(elapsedTime);
+        }
+    }
+
+    private void finalizeResultDirectories(long elapsedTime) {
         info("Invocation completed in %s. %d passed, %d failed, %d not executed",
                 TimeUtil.formatElapsedTime(elapsedTime),
                 mResult.countResults(TestStatus.PASS),
                 mResult.countResults(TestStatus.FAIL),
                 mResult.countResults(TestStatus.NOT_EXECUTED));
-        try {
-            // invocationStarted picked up an unfinished IBuildInfo, so repopulate it now from
-            // the master
-            if (mReloadBuildInfo) {
-                for (Map.Entry<String, String> e : MasterBuildInfo.getBuild().entrySet()) {
-                    mResult.addBuildInfo(e.getKey(), e.getValue());
+
+        // Add all device serials into the result to be serialized
+        for (String deviceSerial : mMasterDeviceSerials) {
+            mResult.addDeviceSerial(deviceSerial);
+        }
+
+        // Add all build info to the result to be serialized
+        for (IBuildInfo buildInfo : mMasterBuildInfos) {
+            for (Map.Entry<String, String> entry : buildInfo.getBuildAttributes().entrySet()) {
+                String key = entry.getKey();
+                if (key.startsWith(BUILD_INFO)) {
+                    mResult.addInvocationInfo(key.substring(CTS_PREFIX.length()), entry.getValue());
                 }
             }
+        }
+        long startTime = mResult.getStartTime();
+        try {
             File resultFile = ResultHandler.writeResults(mBuildHelper.getSuiteName(),
                     mBuildHelper.getSuiteVersion(), mBuildHelper.getSuitePlan(),
-                    mBuildHelper.getSuiteBuild(), mResult, mResultDir, mStartTime,
-                    elapsedTime + mStartTime, mReferenceUrl);
+                    mBuildHelper.getSuiteBuild(), mResult, mResultDir, startTime,
+                    elapsedTime + startTime, mReferenceUrl, mBuildHelper.getCommandLineArgs());
             info("Test Result: %s", resultFile.getCanonicalPath());
+
+            // Zip the full test results directory.
             copyDynamicConfigFiles(mBuildHelper.getDynamicConfigFiles(), mResultDir);
             copyFormattingFiles(mResultDir);
-            // Zip the full test results directory.
             File zippedResults = zipResults(mResultDir);
             info("Full Result: %s", zippedResults.getCanonicalPath());
-            // Save the test result XML.
-            if (mUseLogSaver) {
-                FileInputStream fis = null;
-                try {
-                    fis = new FileInputStream(resultFile);
-                    mLogSaver.saveLogData("log-result", LogDataType.XML, fis);
-                } catch (IOException ioe) {
-                    CLog.e("[%s] error saving XML with log saver", mDeviceSerial);
-                    CLog.e(ioe);
-                } finally {
-                    StreamUtil.close(fis);
-                }
-                // Save the full results folder.
-                if (zippedResults != null) {
-                    FileInputStream zipResultStream = null;
-                    try {
-                        zipResultStream =  new FileInputStream(zippedResults);
-                        mLogSaver.saveLogData("results", LogDataType.ZIP, zipResultStream);
-                    } finally {
-                        StreamUtil.close(zipResultStream);
-                    }
-                }
-            }
-            if (mResultServer != null && !mResultServer.trim().isEmpty() && !mDisableResultPosting) {
-                try {
-                    info("Result Server: %d", mUploader.uploadResult(resultFile, mReferenceUrl));
-                } catch (IOException ioe) {
-                    CLog.e("[%s] IOException while uploading result.", mDeviceSerial);
-                    CLog.e(ioe);
-                }
-            }
+
+            saveLog(resultFile, zippedResults);
+
+            uploadResult(resultFile);
+
         } catch (IOException | XmlPullParserException e) {
             CLog.e("[%s] Exception while saving result XML.", mDeviceSerial);
             CLog.e(e);
-        } finally {
-            MasterBuildInfo.clear();
         }
     }
 
@@ -393,6 +445,7 @@
      */
     @Override
     public void testLog(String name, LogDataType type, InputStreamSource stream) {
+        // This is safe to be invoked on either the master or a shard ResultReporter
         try {
             LogFileSaver saver = new LogFileSaver(mLogDir);
             File logFile = saver.saveAndZipLogData(name, type, stream.createInputStream());
@@ -409,8 +462,10 @@
     @Override
     public void testLogSaved(String dataName, LogDataType dataType, InputStreamSource dataStream,
             LogFile logFile) {
+        // This is safe to be invoked on either the master or a shard ResultReporter
         if (mIncludeTestLogTags && mCurrentResult != null
                 && dataName.startsWith(mCurrentResult.getFullName())) {
+
             if (dataType == LogDataType.BUGREPORT) {
                 mCurrentResult.setBugReport(logFile.getUrl());
             } else if (dataType == LogDataType.LOGCAT) {
@@ -426,10 +481,70 @@
      */
     @Override
     public void setLogSaver(ILogSaver saver) {
+        // This is safe to be invoked on either the master or a shard ResultReporter
         mLogSaver = saver;
     }
 
     /**
+     * When enabled, save log data using log saver
+     */
+    private void saveLog(File resultFile, File zippedResults) throws IOException {
+        if (!mUseLogSaver) {
+            return;
+        }
+
+        FileInputStream fis = null;
+        try {
+            fis = new FileInputStream(resultFile);
+            mLogSaver.saveLogData("log-result", LogDataType.XML, fis);
+        } catch (IOException ioe) {
+            CLog.e("[%s] error saving XML with log saver", mDeviceSerial);
+            CLog.e(ioe);
+        } finally {
+            StreamUtil.close(fis);
+        }
+        // Save the full results folder.
+        if (zippedResults != null) {
+            FileInputStream zipResultStream = null;
+            try {
+                zipResultStream =  new FileInputStream(zippedResults);
+                mLogSaver.saveLogData("results", LogDataType.ZIP, zipResultStream);
+            } finally {
+                StreamUtil.close(zipResultStream);
+            }
+        }
+    }
+
+    @Override
+    public IShardableListener clone() {
+        ResultReporter clone = new ResultReporter(this);
+        OptionCopier.copyOptionsNoThrow(this, clone);
+        return clone;
+    }
+
+    /**
+     * Return true if this instance is a shard ResultReporter and should propagate
+     * certain events to the master.
+     */
+    private boolean isShardResultReporter() {
+        return mMasterResultReporter != null;
+    }
+
+    /**
+     * When enabled, upload the result to a server.
+     */
+    private void uploadResult(File resultFile) throws IOException {
+        if (mResultServer != null && !mResultServer.trim().isEmpty() && !mDisableResultPosting) {
+            try {
+                info("Result Server: %d", mUploader.uploadResult(resultFile, mReferenceUrl));
+            } catch (IOException ioe) {
+                CLog.e("[%s] IOException while uploading result.", mDeviceSerial);
+                CLog.e(ioe);
+            }
+        }
+    }
+
+    /**
      * Copy the xml formatting files stored in this jar to the results directory
      *
      * @param resultsDir
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DeviceInfoCollector.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DeviceInfoCollector.java
index bae4e02..b6cd48f 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DeviceInfoCollector.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DeviceInfoCollector.java
@@ -17,7 +17,6 @@
 package com.android.compatibility.common.tradefed.targetprep;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.compatibility.common.tradefed.build.MasterBuildInfo;
 import com.android.compatibility.common.tradefed.testtype.CompatibilityTest;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.config.Option;
@@ -85,7 +84,6 @@
             buildInfo.addBuildAttribute(entry.getKey(),
                     ArrayUtil.join(",", device.getProperty(entry.getValue())));
         }
-        MasterBuildInfo.addBuildInfo(buildInfo.getBuildAttributes());
         if (mSkipDeviceInfo) {
             return;
         }
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/ReportLogCollector.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/ReportLogCollector.java
index e620f19..a1c8a4a 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/ReportLogCollector.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/ReportLogCollector.java
@@ -27,11 +27,18 @@
 import com.android.tradefed.targetprep.TargetSetupError;
 import com.android.tradefed.util.FileUtil;
 
+import java.io.BufferedReader;
 import java.io.BufferedWriter;
 import java.io.File;
 import java.io.FileNotFoundException;
+import java.io.FileReader;
 import java.io.FileWriter;
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * An {@link ITargetCleaner} that prepares and pulls report logs.
@@ -97,6 +104,7 @@
                 return;
             }
             pull(device, mSrcDir, hostReportDir, resultDir);
+            reformatRepeatedStreams(resultDir);
         } catch (Exception exception) {
             exception.printStackTrace();
         }
@@ -122,4 +130,66 @@
             CLog.e(e);
         }
     }
+
+    private void reformatRepeatedStreams(File resultDir) throws IOException, FileNotFoundException {
+        File[] reportLogs = resultDir.listFiles();
+        // Sometimes report logs are in a sub-directory.
+        if (reportLogs.length == 1 && reportLogs[0].isDirectory()) {
+            reportLogs = reportLogs[0].listFiles();
+        }
+        for (File reportLog : reportLogs) {
+            try (BufferedReader metricsReader = new BufferedReader(new FileReader(reportLog))) {
+                // Get metrics as string.
+                StringBuilder metricBuilder = new StringBuilder();
+                String line;
+                while ((line = metricsReader.readLine()) != null) {
+                    metricBuilder.append(line);
+                }
+                String metrics = metricBuilder.toString();
+                // Create map of stream names and metrics.
+                HashMap<String, List<String>> metricsMap = new HashMap<>();
+                String pattern = "\\\"([a-z0-9_]*)\\\":(\\{[^{}]*\\})";
+                Pattern p = Pattern.compile(pattern);
+                Matcher m = p.matcher(metrics);
+                while (m.find()) {
+                    String key = m.group(1);
+                    String value = m.group(2);
+                    if (!metricsMap.containsKey(key)) {
+                        metricsMap.put(key, new ArrayList<String>());
+                    }
+                    metricsMap.get(key).add(value);
+                }
+                // Rewrite metrics as arrays.
+                StringBuilder newMetricsBuilder = new StringBuilder();
+                newMetricsBuilder.append("{");
+                boolean firstLine = true;
+                for (String key: metricsMap.keySet()) {
+                    if (!firstLine) {
+                        newMetricsBuilder.append(",");
+                    } else {
+                        firstLine = false;
+                    }
+                    newMetricsBuilder.append("\"").append(key).append("\":[");
+                    boolean firstValue = true;
+                    for (String stream : metricsMap.get(key)) {
+                        if (!firstValue) {
+                            newMetricsBuilder.append(",");
+                        }
+                        else {
+                            firstValue = false;
+                        }
+                        newMetricsBuilder.append(stream);
+                    }
+                    newMetricsBuilder.append("]");
+                }
+                newMetricsBuilder.append("}");
+                reportLog.createNewFile();
+                try (BufferedWriter metricsWriter = new BufferedWriter(new
+                        FileWriter(reportLog))) {
+                    String newMetrics = newMetricsBuilder.toString();
+                    metricsWriter.write(newMetrics, 0, newMetrics.length());
+                }
+            }
+        }
+    }
 }
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
index 48b7d3b..16986fb 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
@@ -20,16 +20,20 @@
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.compatibility.common.tradefed.result.IInvocationResultRepo;
 import com.android.compatibility.common.tradefed.result.InvocationResultRepo;
+import com.android.compatibility.common.tradefed.util.OptionHelper;
 import com.android.compatibility.common.util.AbiUtils;
 import com.android.compatibility.common.util.ICaseResult;
 import com.android.compatibility.common.util.IInvocationResult;
 import com.android.compatibility.common.util.IModuleResult;
 import com.android.compatibility.common.util.ITestResult;
 import com.android.compatibility.common.util.MonitoringUtils;
+import com.android.compatibility.common.util.ResultHandler;
 import com.android.compatibility.common.util.TestFilter;
 import com.android.compatibility.common.util.TestStatus;
 import com.android.ddmlib.Log.LogLevel;
 import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.ArgsOptionParser;
+import com.android.tradefed.config.ConfigurationException;
 import com.android.tradefed.config.Option;
 import com.android.tradefed.config.Option.Importance;
 import com.android.tradefed.config.OptionClass;
@@ -57,6 +61,8 @@
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 
 /**
@@ -194,7 +200,6 @@
     private int mTotalShards;
     private IModuleRepo mModuleRepo;
     private ITestDevice mDevice;
-    private IBuildInfo mBuild;
     private CompatibilityBuildHelper mBuildHelper;
 
     /**
@@ -239,9 +244,12 @@
      */
     @Override
     public void setBuild(IBuildInfo buildInfo) {
-        mBuild = buildInfo;
-        mBuildHelper = new CompatibilityBuildHelper(mBuild);
-        mBuildHelper.init(mSuitePlan, mURL);
+        mBuildHelper = new CompatibilityBuildHelper(buildInfo);
+        // Initializing the mBuildHelper also updates properties in buildInfo.
+        // TODO(nicksauer): Keeping invocation properties around via buildInfo
+        // is confusing and would be better done in an "InvocationInfo".
+        // Note, the current time is used to generated the result directory.
+        mBuildHelper.init(mSuitePlan, mURL, System.currentTimeMillis());
     }
 
     /**
@@ -255,12 +263,15 @@
             synchronized (mModuleRepo) {
                 if (!mModuleRepo.isInitialized()) {
                     setupFilters();
+                    // Set retry mode for module repo
+                    mModuleRepo.setRetryMode(mRetrySessionId != null);
                     // Initialize the repository, {@link CompatibilityBuildHelper#getTestsDir} can
                     // throw a {@link FileNotFoundException}
                     mModuleRepo.initialize(mTotalShards, mBuildHelper.getTestsDir(), getAbis(),
                             mDeviceTokens, mTestArgs, mModuleArgs, mIncludeFilters,
-                            mExcludeFilters, mBuild);
+                            mExcludeFilters, mBuildHelper.getBuildInfo());
                 }
+
             }
             // Get the tests to run in this shard
             List<IModuleDef> modules = mModuleRepo.getModules(getDevice().getSerialNumber());
@@ -281,7 +292,7 @@
             // Set values and run preconditions
             for (int i = 0; i < moduleCount; i++) {
                 IModuleDef module = modules.get(i);
-                module.setBuild(mBuild);
+                module.setBuild(mBuildHelper.getBuildInfo());
                 module.setDevice(mDevice);
                 module.setPreparerWhitelist(mPreparerWhitelist);
                 module.prepare(mSkipPreconditions);
@@ -381,18 +392,31 @@
             mModuleName = null;
             mTestName = null;
             // Load the invocation result
-            IInvocationResultRepo repo;
             IInvocationResult result = null;
             try {
-                repo = new InvocationResultRepo(mBuildHelper.getResultsDir());
-                result = repo.getResult(mRetrySessionId);
+                result = ResultHandler.findResult(mBuildHelper.getResultsDir(), mRetrySessionId);
             } catch (FileNotFoundException e) {
-                e.printStackTrace();
+                throw new RuntimeException(e);
             }
             if (result == null) {
                 throw new IllegalArgumentException(String.format(
                         "Could not find session with id %d", mRetrySessionId));
             }
+            CLog.logAndDisplay(LogLevel.INFO, "Retrying session from: %s",
+                    CompatibilityBuildHelper.getDirSuffix(result.getStartTime()));
+
+            String retryCommandLineArgs = result.getCommandLineArgs();
+            if (retryCommandLineArgs != null) {
+                // Copy the original command into the build helper so it can be serialized later
+                mBuildHelper.setRetryCommandLineArgs(retryCommandLineArgs);
+                try {
+                    // parse the command-line string from the result file and set options
+                    ArgsOptionParser parser = new ArgsOptionParser(this);
+                    parser.parse(OptionHelper.getValidCliArgs(retryCommandLineArgs, this));
+                } catch (ConfigurationException e) {
+                    throw new RuntimeException(e);
+                }
+            }
             // Append each test that failed or was not executed to the filters
             for (IModuleResult module : result.getModules()) {
                 for (ICaseResult testResultList : module.getResults()) {
@@ -404,7 +428,8 @@
                     }
                 }
             }
-        } else if (mModuleName != null) {
+        }
+        if (mModuleName != null) {
             mIncludeFilters.clear();
             try {
                 List<String> modules = ModuleRepo.getModuleNamesMatching(
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleDef.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleDef.java
index f18bfc8..4491fb3 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleDef.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleDef.java
@@ -21,14 +21,16 @@
 import com.android.tradefed.testtype.IDeviceTest;
 import com.android.tradefed.testtype.IRemoteTest;
 import com.android.tradefed.testtype.IRuntimeHintProvider;
+import com.android.tradefed.testtype.ITestFileFilterReceiver;
 
+import java.io.File;
 import java.util.Set;
 
 /**
  * Container for Compatibility test info.
  */
 public interface IModuleDef extends Comparable<IModuleDef>, IBuildReceiver, IDeviceTest,
-        IRemoteTest, IRuntimeHintProvider {
+        IRemoteTest, IRuntimeHintProvider, ITestFileFilterReceiver {
 
     /**
      * @return The name of this module.
@@ -72,6 +74,11 @@
     void addExcludeFilter(String name);
 
     /**
+     * @return true if the IModuleDef can handle files of include and exclude filters
+     */
+    boolean isFileFilterReceiver();
+
+    /**
      * Set a list of preparers to allow to run before or after a test.
      * If this list is empty, then all configured preparers will run.
      *
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleRepo.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleRepo.java
index 24e1dec..7f32c10 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleRepo.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleRepo.java
@@ -41,6 +41,11 @@
             List<String> mExcludeFilters, IBuildInfo buildInfo);
 
     /**
+     * Set the retry mode of the module repo.
+     */
+    void setRetryMode(boolean isRetry);
+
+    /**
      * @return a {@link Map} of all modules to run on the device referenced by the given serial.
      */
     List<IModuleDef> getModules(String serial);
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleDef.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleDef.java
index 8d0490b..ae940a5 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleDef.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleDef.java
@@ -38,8 +38,10 @@
 import com.android.tradefed.testtype.IRemoteTest;
 import com.android.tradefed.testtype.IRuntimeHintProvider;
 import com.android.tradefed.testtype.ITestCollector;
+import com.android.tradefed.testtype.ITestFileFilterReceiver;
 import com.android.tradefed.testtype.ITestFilterReceiver;
 
+import java.io.File;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
@@ -191,6 +193,40 @@
      * {@inheritDoc}
      */
     @Override
+    public void setIncludeTestFile(File testFile) {
+        if (!isFileFilterReceiver()) {
+            throw new IllegalArgumentException(
+                    String.format("%s does not implement interface ITestFileFilterReceiver,"
+                    + " cannot setExcludeFile for %s", mTest, mName));
+        }
+        ((ITestFileFilterReceiver)mTest).setIncludeTestFile(testFile);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setExcludeTestFile(File testFile) {
+        if (!isFileFilterReceiver()) {
+            throw new IllegalArgumentException(
+                    String.format("%s does not implement interface ITestFileFilterReceiver,"
+                    + " cannot setExcludeFile for %s", mTest, mName));
+        }
+        ((ITestFileFilterReceiver)mTest).setExcludeTestFile(testFile);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean isFileFilterReceiver() {
+        return (mTest instanceof ITestFileFilterReceiver);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
     public void setPreparerWhitelist(Set<String> preparerWhitelist) {
         mPreparerWhitelist.addAll(preparerWhitelist);
     }
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleRepo.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleRepo.java
index 8e984cb..41a5407 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleRepo.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleRepo.java
@@ -28,10 +28,13 @@
 import com.android.tradefed.testtype.IBuildReceiver;
 import com.android.tradefed.testtype.IRemoteTest;
 import com.android.tradefed.testtype.IShardableTest;
+import com.android.tradefed.util.FileUtil;
 import com.android.tradefed.util.TimeUtil;
 
 import java.io.File;
 import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -63,6 +66,7 @@
     private int mMediumModulesPerShard;
     private int mLargeModulesPerShard;
     private int mModuleCount = 0;
+    private boolean mIsRetry = false;
     private Set<String> mSerials = new HashSet<>();
     private Map<String, Set<String>> mDeviceTokens = new HashMap<>();
     private Map<String, Map<String, String>> mTestArgs = new HashMap<>();
@@ -175,6 +179,11 @@
         return mInitialized;
     }
 
+    @Override
+    public void setRetryMode(boolean isRetry) {
+        mIsRetry = isRetry;
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -333,40 +342,95 @@
 
     private void addModuleDef(IModuleDef moduleDef) {
         String id = moduleDef.getId();
-        boolean includeModule = mIncludeAll;
-        for (TestFilter include : getFilter(mIncludeFilters, id)) {
-            String test = include.getTest();
-            if (test != null) {
-                // We're including a subset of tests
-                moduleDef.addIncludeFilter(test);
-            }
-            includeModule = true;
+        List<TestFilter> mdIncludes = getFilter(mIncludeFilters, id);
+        List<TestFilter> mdExcludes = getFilter(mExcludeFilters, id);
+        if ((!mIncludeAll && mdIncludes.isEmpty()) || containsModuleExclude(mdExcludes)) {
+            // if only including some modules, and no includes exist for this module, do not add
+            // this module. Or if an exclude applies to this entire module, do not add this module.
+            return;
         }
-        for (TestFilter exclude : getFilter(mExcludeFilters, id)) {
-            String test = exclude.getTest();
-            if (test != null) {
-                // Excluding a subset of tests, so keep module but give filter
-                moduleDef.addExcludeFilter(test);
-            } else {
-                // Excluding all tests in the module so just remove the whole thing
-                includeModule = false;
+        if (!mdIncludes.isEmpty()) {
+            addModuleIncludes(moduleDef, mdIncludes);
+        }
+        if (!mdExcludes.isEmpty()) {
+            addModuleExcludes(moduleDef, mdExcludes);
+        }
+
+        Set<String> tokens = moduleDef.getTokens();
+        if (tokens != null && !tokens.isEmpty()) {
+            mTokenModules.add(moduleDef);
+        } else if (moduleDef.getRuntimeHint() < SMALL_TEST) {
+            mSmallModules.add(moduleDef);
+        } else if (moduleDef.getRuntimeHint() < MEDIUM_TEST) {
+            mMediumModules.add(moduleDef);
+        } else {
+            mLargeModules.add(moduleDef);
+        }
+        mModuleCount++;
+    }
+
+    private void addModuleIncludes(IModuleDef moduleDef, List<TestFilter> includes) {
+        if (mIsRetry && moduleDef.isFileFilterReceiver()) {
+            File includeFile = createFilterFile(moduleDef.getName(), ".include", includes);
+            moduleDef.setIncludeTestFile(includeFile);
+        } else {
+            // add module includes one at a time
+            for (TestFilter include : includes) {
+                String test = include.getTest();
+                if (test != null) {
+                    moduleDef.addIncludeFilter(test);
+                }
             }
         }
-        if (includeModule) {
-            Set<String> tokens = moduleDef.getTokens();
-            if (tokens != null && !tokens.isEmpty()) {
-                mTokenModules.add(moduleDef);
-            } else if (moduleDef.getRuntimeHint() < SMALL_TEST) {
-                mSmallModules.add(moduleDef);
-            } else if (moduleDef.getRuntimeHint() < MEDIUM_TEST) {
-                mMediumModules.add(moduleDef);
-            } else {
-                mLargeModules.add(moduleDef);
+    }
+
+    private void addModuleExcludes(IModuleDef moduleDef, List<TestFilter> excludes) {
+        if (mIsRetry && moduleDef.isFileFilterReceiver()) {
+            File excludeFile = createFilterFile(moduleDef.getName(), ".exclude", excludes);
+            moduleDef.setExcludeTestFile(excludeFile);
+        } else {
+            // add module excludes one at a time
+            for (TestFilter exclude : excludes) {
+                moduleDef.addExcludeFilter(exclude.getTest());
             }
-            mModuleCount++;
         }
     }
 
+    private File createFilterFile(String prefix, String suffix, List<TestFilter> filters) {
+        File filterFile = null;
+        PrintWriter out = null;
+        try {
+            filterFile = FileUtil.createTempFile(prefix, suffix);
+            out = new PrintWriter(filterFile);
+            for (TestFilter filter : filters) {
+                String filterTest = filter.getTest();
+                if (filterTest != null) {
+                    out.println(filterTest);
+                }
+            }
+            out.flush();
+        } catch (IOException e) {
+            throw new RuntimeException("Failed to create filter file");
+        } finally {
+            if (out != null) {
+                out.close();
+            }
+        }
+        return filterFile;
+    }
+
+    /*
+     * Returns true iff one or more test filters in excludes apply to the entire module.
+     */
+    private boolean containsModuleExclude(Collection<TestFilter> excludes) {
+        for (TestFilter exclude : excludes) {
+            if (exclude.getTest() == null) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * A {@link FilenameFilter} to find all the config files in a directory.
      */
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/util/OptionHelper.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/util/OptionHelper.java
new file mode 100644
index 0000000..23832a2
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/util/OptionHelper.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2016 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.compatibility.common.tradefed.util;
+
+import com.android.tradefed.config.Option;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Helper class for manipulating fields with @option annotations.
+ */
+public final class OptionHelper {
+
+    private OptionHelper() {}
+
+    /**
+     * Return the {@link List} of {@link Field} entries on the given object
+     * that have the {@link Option} annotation.
+     *
+     * @param object An object with @option-annotated fields.
+     */
+    private static List<Field> getFields(Object object) {
+        Field[] classFields = object.getClass().getDeclaredFields();
+        List<Field> optionFields = new ArrayList<Field>();
+
+        for (Field declaredField : classFields) {
+            // allow access to protected and private fields
+            declaredField.setAccessible(true);
+
+            // store type and values only in annotated fields
+            if (declaredField.isAnnotationPresent(Option.class)) {
+                optionFields.add(declaredField);
+            }
+        }
+        return optionFields;
+    }
+
+    /**
+     * Retrieve a {@link Set} of {@link Option} names present on the given
+     * object.
+     *
+     * @param object An object with @option-annotated fields.
+     */
+    static Set<String> getOptionNames(Object object) {
+        Set<String> options = new HashSet<String>();
+        List<Field> optionFields = getFields(object);
+
+        for (Field declaredField : optionFields) {
+            Option option = declaredField.getAnnotation(Option.class);
+            options.add(option.name());
+        }
+        return options;
+    }
+
+    /**
+     * Retrieve a {@link Set} of {@link Option} short names present on the given
+     * object.
+     *
+     * @param object An object with @option-annotated fields.
+     */
+    static Set<String> getOptionShortNames(Object object) {
+        Set<String> shortNames = new HashSet<String>();
+        List<Field> optionFields = getFields(object);
+
+        for (Field declaredField : optionFields) {
+            Option option = declaredField.getAnnotation(Option.class);
+            if (option.shortName() != Option.NO_SHORT_NAME) {
+                shortNames.add(String.valueOf(option.shortName()));
+            }
+        }
+        return shortNames;
+    }
+
+    /**
+     * Retrieve a {@link List} of {@link String} entries of the valid
+     * command-line options for the given {@link Object} from the given
+     * input {@link String}.
+     */
+    public static List<String> getValidCliArgs(String commandString, Object object) {
+        Set<String> optionNames = OptionHelper.getOptionNames(object);
+        Set<String> optionShortNames = OptionHelper.getOptionShortNames(object);
+        List<String> validCliArgs = new ArrayList<String>();
+
+        // get "-option/--option value" pairs from the command-line string
+        Pattern cliPattern = Pattern.compile("-[-\\w]+[ =]\\S+");
+        Matcher matcher = cliPattern.matcher(commandString);
+
+        while (matcher.find()) {
+            String match = matcher.group();
+            // split between the option name and value
+            String[] tokens = match.split("[ =]");
+            // remove initial hyphens from option args
+            String keyName = tokens[0].replaceFirst("^--?", "");
+
+            // add substrings only when the options are recognized
+            if (optionShortNames.contains(keyName) || optionNames.contains(keyName)) {
+                if (match.contains(" ")) {
+                    // ArgsOptionParser expect stand-alone entity or a key=value format, no spaces.
+                    validCliArgs.add(match.split(" ")[0]);
+                    validCliArgs.add(match.split(" ")[1]);
+                } else {
+                    validCliArgs.add(match);
+                }
+            }
+        }
+        return validCliArgs;
+    }
+}
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
index 318960c..b710128 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
@@ -17,12 +17,14 @@
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelperTest;
 import com.android.compatibility.common.tradefed.command.CompatibilityConsoleTest;
+import com.android.compatibility.common.tradefed.result.ConsoleReporterTest;
 import com.android.compatibility.common.tradefed.result.ResultReporterTest;
 import com.android.compatibility.common.tradefed.targetprep.PropertyCheckTest;
 import com.android.compatibility.common.tradefed.targetprep.SettingsPreparerTest;
 import com.android.compatibility.common.tradefed.testtype.CompatibilityTestTest;
 import com.android.compatibility.common.tradefed.testtype.ModuleDefTest;
 import com.android.compatibility.common.tradefed.testtype.ModuleRepoTest;
+import com.android.compatibility.common.tradefed.util.OptionHelperTest;
 
 import junit.framework.Test;
 import junit.framework.TestSuite;
@@ -38,8 +40,11 @@
         super();
         addTestSuite(CompatibilityBuildHelperTest.class);
         addTestSuite(CompatibilityConsoleTest.class);
+        addTestSuite(CompatibilityTestTest.class);
+        addTestSuite(ConsoleReporterTest.class);
         addTestSuite(ResultReporterTest.class);
         addTestSuite(CompatibilityTestTest.class);
+        addTestSuite(OptionHelperTest.class);
         addTestSuite(ModuleDefTest.class);
         addTestSuite(ModuleRepoTest.class);
         addTestSuite(PropertyCheckTest.class);
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelperTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelperTest.java
index 55d15dd..503c529 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelperTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/build/CompatibilityBuildHelperTest.java
@@ -36,6 +36,8 @@
     private static final String ROOT_DIR_NAME = "root";
     private static final String BASE_DIR_NAME = "android-tests";
     private static final String TESTCASES = "testcases";
+    private static final String COMMAND_LINE_ARGS = "cts -m CtsModuleTestCases";
+    private static final long START_TIME = 123456L;
 
     private File mRoot = null;
     private File mBase = null;
@@ -69,7 +71,7 @@
 
     public void testSuiteInfoLoad() throws Exception {
         setProperty(mRoot.getAbsolutePath());
-        mHelper.init(SUITE_PLAN, DYNAMIC_CONFIG_URL);
+        mHelper.init(SUITE_PLAN, DYNAMIC_CONFIG_URL, START_TIME);
         assertEquals("Incorrect suite build number", BUILD_NUMBER, mHelper.getSuiteBuild());
         assertEquals("Incorrect suite name", SUITE_NAME, mHelper.getSuiteName());
         assertEquals("Incorrect suite full name", SUITE_FULL_NAME, mHelper.getSuiteFullName());
@@ -82,19 +84,19 @@
         CompatibilityBuildHelper helper = new CompatibilityBuildHelper(provider.getBuild());
         try {
             // Should fail with root unset
-            helper.init(SUITE_PLAN, DYNAMIC_CONFIG_URL);
+            helper.init(SUITE_PLAN, DYNAMIC_CONFIG_URL, START_TIME);
             fail("Expected fail for unset root property");
         } catch (IllegalArgumentException e) {
             /* expected */
         }
         setProperty(mRoot.getAbsolutePath());
         // Shouldn't fail with root set
-        helper.init(SUITE_PLAN, DYNAMIC_CONFIG_URL);
+        helper.init(SUITE_PLAN, DYNAMIC_CONFIG_URL, START_TIME);
     }
 
     public void testValidation() throws Exception {
         setProperty(mRoot.getAbsolutePath());
-        mHelper.init(SUITE_PLAN, DYNAMIC_CONFIG_URL);
+        mHelper.init(SUITE_PLAN, DYNAMIC_CONFIG_URL, START_TIME);
         try {
             mHelper.getDir();
             fail("Build helper validation succeeded on an invalid installation");
@@ -112,7 +114,7 @@
 
     public void testDirs() throws Exception {
         setProperty(mRoot.getAbsolutePath());
-        mHelper.init(SUITE_PLAN, DYNAMIC_CONFIG_URL);
+        mHelper.init(SUITE_PLAN, DYNAMIC_CONFIG_URL, START_TIME);
         createDirStructure();
         assertNotNull(mRoot);
         assertNotNull(mBuild);
@@ -129,6 +131,16 @@
                 mHelper.getResultsDir().getAbsolutePath());
     }
 
+    public void testGetCommandLineArgs() {
+        assertNull(mHelper.getCommandLineArgs());
+        mBuild.addBuildAttribute("command_line_args", COMMAND_LINE_ARGS);
+        assertEquals(COMMAND_LINE_ARGS, mHelper.getCommandLineArgs());
+
+        mBuild.addBuildAttribute("command_line_args", "cts --retry 0");
+        mHelper.setRetryCommandLineArgs(COMMAND_LINE_ARGS);
+        assertEquals(COMMAND_LINE_ARGS, mHelper.getCommandLineArgs());
+    }
+
     /**
      * Sets the *_ROOT property of the build's installation location.
      *
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ConsoleReporterTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ConsoleReporterTest.java
new file mode 100644
index 0000000..38fb1db
--- /dev/null
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ConsoleReporterTest.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.tradefed.result;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.compatibility.common.util.AbiUtils;
+import com.android.compatibility.common.util.ICaseResult;
+import com.android.compatibility.common.util.IInvocationResult;
+import com.android.compatibility.common.util.IModuleResult;
+import com.android.compatibility.common.util.ITestResult;
+import com.android.compatibility.common.util.TestStatus;
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.tradefed.build.BuildInfo;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.OptionSetter;
+import com.android.tradefed.util.FileUtil;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.util.HashMap;
+import java.util.List;
+
+public class ConsoleReporterTest extends TestCase {
+
+    private static final String TESTCASES = "testcases";
+    private static final String NAME = "ModuleName";
+    private static final String NAME2 = "ModuleName2";
+    private static final String ABI = "mips64";
+    private static final String ID = AbiUtils.createId(ABI, NAME);
+    private static final String ID2 = AbiUtils.createId(ABI, NAME2);
+    private static final String CLASS = "android.test.FoorBar";
+    private static final String METHOD_1 = "testBlah1";
+    private static final String METHOD_2 = "testBlah2";
+    private static final String METHOD_3 = "testBlah3";
+    private static final String TEST_1 = String.format("%s#%s", CLASS, METHOD_1);
+    private static final String TEST_2 = String.format("%s#%s", CLASS, METHOD_2);
+    private static final String TEST_3 = String.format("%s#%s", CLASS, METHOD_3);
+    private static final String STACK_TRACE = "Something small is not alright\n " +
+            "at four.big.insects.Marley.sing(Marley.java:10)";
+
+    private ConsoleReporter mReporter;
+    private IBuildInfo mBuildInfo;
+    private CompatibilityBuildHelper mBuildHelper;
+
+    private File mRoot = null;
+    private File mBase = null;
+    private File mTests = null;
+
+    @Override
+    public void setUp() throws Exception {
+        mReporter = new ConsoleReporter();
+        OptionSetter setter = new OptionSetter(mReporter);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        mReporter = null;
+    }
+
+    public void testResultReporting_singleModule() throws Exception {
+        mReporter.invocationStarted(mBuildInfo);
+        mReporter.testRunStarted(ID, 3);
+        runTests();
+
+        mReporter.testRunEnded(10, new HashMap<String, String>());
+        mReporter.invocationEnded(10);
+
+        assertEquals(ID, mReporter.getModuleId());
+        assertEquals(2, mReporter.getFailedTests());
+        assertEquals(1, mReporter.getPassedTests());
+        assertEquals(3, mReporter.getCurrentTestNum());
+        assertEquals(3, mReporter.getTotalTestsInModule());
+    }
+
+    public void testResultReporting_multipleModules() throws Exception {
+        mReporter.invocationStarted(mBuildInfo);
+        mReporter.testRunStarted(ID, 3);
+        runTests();
+
+        assertEquals(ID, mReporter.getModuleId());
+        assertEquals(2, mReporter.getFailedTests());
+        assertEquals(1, mReporter.getPassedTests());
+        assertEquals(3, mReporter.getCurrentTestNum());
+        assertEquals(3, mReporter.getTotalTestsInModule());
+
+        // Should reset counters
+        mReporter.testRunStarted(ID2, 3);
+        assertEquals(ID2, mReporter.getModuleId());
+        assertEquals(0, mReporter.getFailedTests());
+        assertEquals(0, mReporter.getPassedTests());
+        assertEquals(0, mReporter.getCurrentTestNum());
+        assertEquals(3, mReporter.getTotalTestsInModule());
+
+        runTests();
+        // Same id, should not reset test counters, but aggregate total tests
+        mReporter.testRunStarted(ID2, 5);
+        assertEquals(ID2, mReporter.getModuleId());
+        assertEquals(2, mReporter.getFailedTests());
+        assertEquals(1, mReporter.getPassedTests());
+        assertEquals(3, mReporter.getCurrentTestNum());
+        assertEquals(8, mReporter.getTotalTestsInModule());
+    }
+
+    private void runTests() {
+        TestIdentifier test1 = new TestIdentifier(CLASS, METHOD_1);
+        mReporter.testStarted(test1);
+        mReporter.testEnded(test1, new HashMap<String, String>());
+        assertFalse(mReporter.getTestFailed());
+
+        TestIdentifier test2 = new TestIdentifier(CLASS, METHOD_2);
+        mReporter.testStarted(test2);
+        assertFalse(mReporter.getTestFailed());
+        mReporter.testFailed(test2, STACK_TRACE);
+        assertTrue(mReporter.getTestFailed());
+
+        TestIdentifier test3 = new TestIdentifier(CLASS, METHOD_3);
+        mReporter.testStarted(test3);
+        assertFalse(mReporter.getTestFailed());
+        mReporter.testFailed(test3, STACK_TRACE);
+        assertTrue(mReporter.getTestFailed());
+    }
+}
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ResultReporterTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ResultReporterTest.java
index 6228cd7..60c48b3 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ResultReporterTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ResultReporterTest.java
@@ -63,6 +63,7 @@
         "compatibility_result.xsd",
         "compatibility_result.xsl",
         "logo.png"};
+    private static final long START_TIME = 123456L;
 
     private ResultReporter mReporter;
     private IBuildInfo mBuildInfo;
@@ -76,7 +77,6 @@
     public void setUp() throws Exception {
         mReporter = new ResultReporter();
         OptionSetter setter = new OptionSetter(mReporter);
-        setter.setOptionValue("quiet-output", "true");
         mRoot = FileUtil.createTempDir(ROOT_DIR_NAME);
         mBase = new File(mRoot, BASE_DIR_NAME);
         mBase.mkdirs();
@@ -85,7 +85,7 @@
         System.setProperty(ROOT_PROPERTY, mRoot.getAbsolutePath());
         mBuildInfo = new BuildInfo(BUILD_NUMBER, "", "");
         mBuildHelper = new CompatibilityBuildHelper(mBuildInfo);
-        mBuildHelper.init(SUITE_PLAN, DYNAMIC_CONFIG_URL);
+        mBuildHelper.init(SUITE_PLAN, DYNAMIC_CONFIG_URL, START_TIME);
     }
 
     @Override
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/util/OptionHelperTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/util/OptionHelperTest.java
new file mode 100644
index 0000000..4f3d48f
--- /dev/null
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/util/OptionHelperTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2016 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.compatibility.common.tradefed.util;
+
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.Option.Importance;
+
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Unit tests for {@link OptionHelper}
+ */
+public class OptionHelperTest extends TestCase {
+
+    private static final String TEST_CLASS = "test-class";
+    private static final String TEST_CLASS_SHORTNAME = "c";
+    private static final String TEST_NAME = "test-name";
+    private static final String TEST_SUITE = "test-suite";
+    private static final String TEST_SUITE_SHORTNAME = "s";
+
+    @Option(name = TEST_CLASS,
+            shortName = 'c',
+            importance = Importance.ALWAYS)
+    private String mTestClass = null;
+
+    @Option(name = TEST_NAME,
+            importance = Importance.ALWAYS)
+    private String mTestName = null;
+
+    @Option(name = TEST_SUITE,
+            shortName = 's',
+            importance = Importance.ALWAYS)
+    private String mTestSuite = null;
+
+    public void testGetOptionNames() throws Exception {
+        Set<String> optionNames = OptionHelper.getOptionNames(this);
+        List<String> expectedNames = Arrays.asList(TEST_CLASS, TEST_NAME, TEST_SUITE);
+        assertEquals("Missing option names", true, optionNames.containsAll(expectedNames));
+        assertEquals("Expected three elements", 3, optionNames.size());
+    }
+
+    public void testGetOptionShortNames() throws Exception {
+        Set<String> optionShortNames = OptionHelper.getOptionShortNames(this);
+        List<String> expectedShortNames = Arrays.asList(TEST_CLASS_SHORTNAME, TEST_SUITE_SHORTNAME);
+        assertEquals("Missing option shortnames", true,
+            optionShortNames.containsAll(expectedShortNames));
+        assertEquals("Expected two elements", 2, optionShortNames.size());
+    }
+
+    public void testGetValidCliArgs() throws Exception {
+        List<String> noValidNames = new ArrayList<String>();
+        List<String> validSubset = Arrays.asList("--" + TEST_CLASS, "fooclass",
+            "-" + TEST_SUITE_SHORTNAME, "foosuite");
+        List<String> allValidNames = Arrays.asList("--" + TEST_CLASS, "fooclass",
+            "-" + TEST_SUITE_SHORTNAME, "foosuite", "--" + TEST_NAME, "footest");
+
+        assertEquals("Expected no valid names", noValidNames,
+            OptionHelper.getValidCliArgs("test --foo -b", this));
+        assertEquals("Expected one long name and one short name", validSubset,
+            OptionHelper.getValidCliArgs("test --" + TEST_CLASS + " fooclass -b fake"
+                + " -s foosuite", this));
+        assertEquals("Expected two long names and one short name", allValidNames,
+            OptionHelper.getValidCliArgs("test --" + TEST_CLASS + " fooclass -b fake"
+                + " -s foosuite " + "--" + TEST_NAME + " footest", this));
+    }
+
+}
diff --git a/common/util/src/com/android/compatibility/common/util/CaseResult.java b/common/util/src/com/android/compatibility/common/util/CaseResult.java
index e16ad1f..36f77d7 100644
--- a/common/util/src/com/android/compatibility/common/util/CaseResult.java
+++ b/common/util/src/com/android/compatibility/common/util/CaseResult.java
@@ -112,4 +112,20 @@
         return getName().compareTo(another.getName());
     }
 
-}
\ No newline at end of file
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void mergeFrom(ICaseResult otherCaseResult) {
+        if (!otherCaseResult.getName().equals(getName())) {
+            throw new IllegalArgumentException(String.format(
+                "Cannot merge case result with mismatched name. Expected %s, Found %d",
+                        otherCaseResult.getName(), getName()));
+        }
+
+        for (ITestResult otherTestResult : otherCaseResult.getResults()) {
+            mResults.put(otherTestResult.getName(), otherTestResult);
+        }
+    }
+
+}
diff --git a/common/util/src/com/android/compatibility/common/util/ICaseResult.java b/common/util/src/com/android/compatibility/common/util/ICaseResult.java
index 99e646a0..5904d69 100644
--- a/common/util/src/com/android/compatibility/common/util/ICaseResult.java
+++ b/common/util/src/com/android/compatibility/common/util/ICaseResult.java
@@ -55,4 +55,8 @@
      */
     int countResults(TestStatus status);
 
+    /**
+     * Merge the case results from otherCaseResult into this caseResult.
+     */
+    void mergeFrom(ICaseResult otherCaseResult);
 }
diff --git a/common/util/src/com/android/compatibility/common/util/IInvocationResult.java b/common/util/src/com/android/compatibility/common/util/IInvocationResult.java
index c1e5d28..3e73ede 100644
--- a/common/util/src/com/android/compatibility/common/util/IInvocationResult.java
+++ b/common/util/src/com/android/compatibility/common/util/IInvocationResult.java
@@ -71,17 +71,27 @@
     List<IModuleResult> getModules();
 
     /**
-     * @return the directory containing this result.
+     * Merges a module result to the invocation result.
      */
-    File getResultDir();
+    void mergeModuleResult(IModuleResult moduleResult);
 
     /**
-     * Adds the given build info to the result.
+     * Adds the given invocation info to the result.
      */
-    void addBuildInfo(String key, String value);
+    void addInvocationInfo(String key, String value);
 
     /**
-     * Gets the {@link Map} of build info collected.
+     * Gets the {@link Map} of invocation info collected.
      */
-    Map<String, String> getBuildInfo();
+    Map<String, String> getInvocationInfo();
+
+    /**
+     *  Set the string containing the command line arguments to the run command.
+     */
+    void setCommandLineArgs(String setCommandLineArgs);
+
+    /**
+     * Retrieve the command line arguments to the run command.
+     */
+    String getCommandLineArgs();
 }
diff --git a/common/util/src/com/android/compatibility/common/util/IModuleResult.java b/common/util/src/com/android/compatibility/common/util/IModuleResult.java
index 6d4efa1..f861b51 100644
--- a/common/util/src/com/android/compatibility/common/util/IModuleResult.java
+++ b/common/util/src/com/android/compatibility/common/util/IModuleResult.java
@@ -58,4 +58,8 @@
      */
     int countResults(TestStatus status);
 
+    /**
+     * Merge the module results from otherModuleResult into this moduleResult.
+     */
+    void mergeFrom(IModuleResult otherModuleResult);
 }
diff --git a/common/util/src/com/android/compatibility/common/util/InvocationResult.java b/common/util/src/com/android/compatibility/common/util/InvocationResult.java
index b499bc1..a94dea8 100644
--- a/common/util/src/com/android/compatibility/common/util/InvocationResult.java
+++ b/common/util/src/com/android/compatibility/common/util/InvocationResult.java
@@ -15,7 +15,6 @@
  */
 package com.android.compatibility.common.util;
 
-import java.io.File;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -32,26 +31,10 @@
 
     private long mTimestamp;
     private Map<String, IModuleResult> mModuleResults = new LinkedHashMap<>();
-    private Map<String, String> mBuildInfo = new HashMap<>();
+    private Map<String, String> mInvocationInfo = new HashMap<>();
     private Set<String> mSerials = new HashSet<>();
     private String mTestPlan;
-    private File mResultDir;
-
-    /**
-     * @param resultDir
-     */
-    public InvocationResult(File resultDir) {
-        this(System.currentTimeMillis(), resultDir);
-    }
-
-    /**
-     * @param timestamp
-     * @param resultDir
-     */
-    public InvocationResult(long timestamp, File resultDir) {
-        setStartTime(timestamp);
-        mResultDir = resultDir;
-    }
+    private String mCommandLineArgs;
 
     /**
      * {@inheritDoc}
@@ -92,16 +75,26 @@
      * {@inheritDoc}
      */
     @Override
-    public void addBuildInfo(String key, String value) {
-        mBuildInfo.put(key, value);
+    public void mergeModuleResult(IModuleResult moduleResult) {
+        // Merge the moduleResult with any existing module result
+        IModuleResult existingModuleResult = getOrCreateModule(moduleResult.getId());
+        existingModuleResult.mergeFrom(moduleResult);
     }
 
     /**
      * {@inheritDoc}
      */
     @Override
-    public Map<String, String> getBuildInfo() {
-        return mBuildInfo;
+    public void addInvocationInfo(String key, String value) {
+        mInvocationInfo.put(key, value);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Map<String, String> getInvocationInfo() {
+        return mInvocationInfo;
     }
 
     /**
@@ -156,8 +149,15 @@
      * {@inheritDoc}
      */
     @Override
-    public File getResultDir() {
-        return mResultDir;
+    public void setCommandLineArgs(String commandLineArgs) {
+        mCommandLineArgs = commandLineArgs;
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getCommandLineArgs() {
+        return mCommandLineArgs;
+    }
 }
diff --git a/common/util/src/com/android/compatibility/common/util/ModuleResult.java b/common/util/src/com/android/compatibility/common/util/ModuleResult.java
index 1f277f7..7e93824 100644
--- a/common/util/src/com/android/compatibility/common/util/ModuleResult.java
+++ b/common/util/src/com/android/compatibility/common/util/ModuleResult.java
@@ -130,4 +130,21 @@
         return getId().compareTo(another.getId());
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void mergeFrom(IModuleResult otherModuleResult) {
+        if (!otherModuleResult.getId().equals(getId())) {
+            throw new IllegalArgumentException(String.format(
+                "Cannot merge module result with mismatched id. Expected %s, Found %s",
+                        otherModuleResult.getId(), getId()));
+        }
+
+        this.mRuntime += otherModuleResult.getRuntime();
+        for (ICaseResult otherCaseResult : otherModuleResult.getResults()) {
+            ICaseResult caseResult = getOrCreateResult(otherCaseResult.getName());
+            caseResult.mergeFrom(otherCaseResult);
+        }
+    }
 }
diff --git a/common/util/src/com/android/compatibility/common/util/ResultHandler.java b/common/util/src/com/android/compatibility/common/util/ResultHandler.java
index 8e435d8..c56ff1a 100644
--- a/common/util/src/com/android/compatibility/common/util/ResultHandler.java
+++ b/common/util/src/com/android/compatibility/common/util/ResultHandler.java
@@ -30,6 +30,8 @@
 import java.net.UnknownHostException;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Collections;
 import java.util.Date;
 import java.util.List;
 import java.util.Map.Entry;
@@ -53,6 +55,7 @@
     private static final String BUILD_PRODUCT = "build_product";
     private static final String BUILD_TAG = "Build";
     private static final String CASE_TAG = "TestCase";
+    private static final String COMMAND_LINE_ARGS = "command_line_args";
     private static final String DEVICES_ATTR = "devices";
     private static final String END_DISPLAY_TIME_ATTR = "end_display";
     private static final String END_TIME_ATTR = "end";
@@ -105,7 +108,7 @@
                 if (!resultFile.exists()) {
                     continue;
                 }
-                IInvocationResult invocation = new InvocationResult(resultDir);
+                IInvocationResult invocation = new InvocationResult();
                 XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
                 XmlPullParser parser = factory.newPullParser();
                 parser.setInput(new FileReader(resultFile));
@@ -115,6 +118,7 @@
                 invocation.setStartTime(Long.valueOf(
                         parser.getAttributeValue(NS, START_TIME_ATTR)));
                 invocation.setTestPlan(parser.getAttributeValue(NS, SUITE_PLAN_ATTR));
+                invocation.setCommandLineArgs(parser.getAttributeValue(NS, COMMAND_LINE_ARGS));
                 String deviceList = parser.getAttributeValue(NS, DEVICES_ATTR);
                 for (String device : deviceList.split(",")) {
                     invocation.addDeviceSerial(device);
@@ -122,8 +126,9 @@
 
                 parser.nextTag();
                 parser.require(XmlPullParser.START_TAG, NS, BUILD_TAG);
-                invocation.addBuildInfo(BUILD_ID, parser.getAttributeValue(NS, BUILD_ID));
-                invocation.addBuildInfo(BUILD_PRODUCT, parser.getAttributeValue(NS, BUILD_PRODUCT));
+                invocation.addInvocationInfo(BUILD_ID, parser.getAttributeValue(NS, BUILD_ID));
+                invocation.addInvocationInfo(BUILD_PRODUCT, parser.getAttributeValue(NS,
+                    BUILD_PRODUCT));
 
                 // TODO(stuartscott): may want to reload these incase the retry was done with
                 // --skip-device-info flag
@@ -190,6 +195,12 @@
                 e.printStackTrace();
             }
         }
+        // Sort the table entries on each entry's timestamp.
+        Collections.sort(results, new Comparator<IInvocationResult>() {
+            public int compare(IInvocationResult result1, IInvocationResult result2) {
+                return Long.compare(result1.getStartTime(), result2.getStartTime());
+            }
+        });
         return results;
     }
 
@@ -198,13 +209,14 @@
      * @param resultDir
      * @param startTime
      * @param referenceUrl A nullable string that can contain a URL to a related data
+     * @param commandLineArgs A string containing the arguments to the run command
      * @return The result file created.
      * @throws IOException
      * @throws XmlPullParserException
      */
     public static File writeResults(String suiteName, String suiteVersion, String suitePlan,
             String suiteBuild, IInvocationResult result, File resultDir,
-            long startTime, long endTime, String referenceUrl)
+            long startTime, long endTime, String referenceUrl, String commandLineArgs)
                     throws IOException, XmlPullParserException {
         int passed = result.countResults(TestStatus.PASS);
         int failed = result.countResults(TestStatus.FAIL);
@@ -228,6 +240,8 @@
         serializer.attribute(NS, SUITE_PLAN_ATTR, suitePlan);
         serializer.attribute(NS, SUITE_BUILD_ATTR, suiteBuild);
         serializer.attribute(NS, REPORT_VERSION_ATTR, RESULT_FILE_VERSION);
+        serializer.attribute(NS, COMMAND_LINE_ARGS, nullToEmpty(commandLineArgs));
+
         if (referenceUrl != null) {
             serializer.attribute(NS, REFERENCE_URL_ATTR, referenceUrl);
         }
@@ -260,7 +274,7 @@
 
         // Build Info
         serializer.startTag(NS, BUILD_TAG);
-        for (Entry<String, String> entry : result.getBuildInfo().entrySet()) {
+        for (Entry<String, String> entry : result.getInvocationInfo().entrySet()) {
             serializer.attribute(NS, entry.getKey(), entry.getValue());
         }
         serializer.endTag(NS, BUILD_TAG);
@@ -330,6 +344,23 @@
     }
 
     /**
+     * Find the IInvocationResult for the given sessionId.
+     */
+    public static IInvocationResult findResult(File resultsDir, Integer sessionId)
+            throws FileNotFoundException {
+        if (sessionId < 0) {
+            throw new IllegalArgumentException(
+                String.format("Invalid session id [%d] ", sessionId));
+        }
+
+        List<IInvocationResult> results = getResults(resultsDir);
+        if (results == null || sessionId >= results.size()) {
+            throw new RuntimeException(String.format("Could not find session [%d]", sessionId));
+        }
+        return results.get(sessionId);
+    }
+
+    /**
      * Return the given time as a {@link String} suitable for displaying.
      * <p/>
      * Example: Fri Aug 20 15:13:03 PDT 2010
@@ -340,4 +371,11 @@
         SimpleDateFormat dateFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy");
         return dateFormat.format(new Date(time));
     }
+
+    /**
+     * When nullable is null, return an empty string. Otherwise, return the value in nullable.
+     */
+    private static String nullToEmpty(String nullable) {
+        return nullable == null ? "" : nullable;
+    }
 }
diff --git a/common/util/src/com/android/compatibility/common/util/TestFilter.java b/common/util/src/com/android/compatibility/common/util/TestFilter.java
index 47ed9ec..460e34f 100644
--- a/common/util/src/com/android/compatibility/common/util/TestFilter.java
+++ b/common/util/src/com/android/compatibility/common/util/TestFilter.java
@@ -94,15 +94,15 @@
     public String toString() {
         StringBuilder sb = new StringBuilder();
         if (mAbi != null) {
-            sb.append(mAbi);
+            sb.append(mAbi.trim());
             sb.append(" ");
         }
         if (mName != null) {
-            sb.append(mName);
+            sb.append(mName.trim());
         }
         if (mTest != null) {
             sb.append(" ");
-            sb.append(mTest);
+            sb.append(mTest.trim());
         }
         return sb.toString();
     }
diff --git a/common/util/tests/src/com/android/compatibility/common/util/CaseResultTest.java b/common/util/tests/src/com/android/compatibility/common/util/CaseResultTest.java
index ae29597..c8b74c0 100644
--- a/common/util/tests/src/com/android/compatibility/common/util/CaseResultTest.java
+++ b/common/util/tests/src/com/android/compatibility/common/util/CaseResultTest.java
@@ -76,4 +76,45 @@
         assertEquals("Expected two failures", 2, mResult.countResults(TestStatus.FAIL));
         assertEquals("Expected one pass", 1, mResult.countResults(TestStatus.PASS));
     }
-}
\ No newline at end of file
+
+    public void testMergeCase() throws Exception {
+        mResult.getOrCreateResult(METHOD_1).failed(STACK_TRACE);
+        mResult.getOrCreateResult(METHOD_2).passed(null);
+
+        // Same case another test and passing results in method 2
+        CaseResult otherResult = new CaseResult(CLASS);
+        otherResult.getOrCreateResult(METHOD_1).passed(null);
+        otherResult.getOrCreateResult(METHOD_2).passed(null);
+        otherResult.getOrCreateResult(METHOD_3).failed(STACK_TRACE);
+
+        mResult.mergeFrom(otherResult);
+        assertEquals("Expected one result", 3, mResult.getResults().size());
+        assertEquals("Expected one failures", 1, mResult.countResults(TestStatus.FAIL));
+        assertEquals("Expected two pass", 2, mResult.countResults(TestStatus.PASS));
+    }
+
+     public void testMergeCase_passToFail() throws Exception {
+        mResult.getOrCreateResult(METHOD_1).passed(null);
+
+        // Same case another test and passing results in method 2
+        CaseResult otherResult = new CaseResult(CLASS);
+        otherResult.getOrCreateResult(METHOD_1).passed(null);
+        otherResult.getOrCreateResult(METHOD_2).passed(null);
+        otherResult.getOrCreateResult(METHOD_3).failed(STACK_TRACE);
+
+        mResult.mergeFrom(otherResult);
+
+        assertEquals("Expected one result", 3, mResult.getResults().size());
+        assertEquals("Expected one failures", 1, mResult.countResults(TestStatus.FAIL));
+        assertEquals("Expected two pass", 2, mResult.countResults(TestStatus.PASS));
+    }
+
+    public void testMergeCase_mismatchedModuleName() throws Exception {
+
+        CaseResult otherResult = new CaseResult(CLASS + "foo");
+        try {
+            mResult.mergeFrom(otherResult);
+            fail("Expected IlleglArgumentException");
+        } catch (IllegalArgumentException expected) {}
+    }
+}
diff --git a/common/util/tests/src/com/android/compatibility/common/util/ModuleResultTest.java b/common/util/tests/src/com/android/compatibility/common/util/ModuleResultTest.java
index 2239ac8..497623c 100644
--- a/common/util/tests/src/com/android/compatibility/common/util/ModuleResultTest.java
+++ b/common/util/tests/src/com/android/compatibility/common/util/ModuleResultTest.java
@@ -24,9 +24,12 @@
 public class ModuleResultTest extends TestCase {
 
     private static final String NAME = "ModuleName";
+    private static final String NAME_2 = "ModuleName2";
     private static final String ABI = "mips64";
     private static final String ID = AbiUtils.createId(ABI, NAME);
+    private static final String ID_2 = AbiUtils.createId(ABI, NAME_2);
     private static final String CLASS = "android.test.FoorBar";
+    private static final String CLASS_2 = "android.test.FoorBar2";
     private static final String METHOD_1 = "testBlah1";
     private static final String METHOD_2 = "testBlah2";
     private static final String METHOD_3 = "testBlah3";
@@ -69,4 +72,40 @@
         assertEquals("Expected two failures", 2, mResult.countResults(TestStatus.FAIL));
         assertEquals("Expected one pass", 1, mResult.countResults(TestStatus.PASS));
     }
-}
\ No newline at end of file
+
+    public void testMergeModule() throws Exception {
+        ICaseResult caseResult = mResult.getOrCreateResult(CLASS);
+        caseResult.getOrCreateResult(METHOD_1).failed(STACK_TRACE);
+        caseResult.getOrCreateResult(METHOD_3).passed(null);
+
+        ICaseResult caseResult2 = mResult.getOrCreateResult(CLASS_2);
+        caseResult2.getOrCreateResult(METHOD_1).failed(STACK_TRACE);
+        caseResult2.getOrCreateResult(METHOD_3).passed(null);
+
+        assertEquals("Expected two results", 2, mResult.getResults().size());
+        assertTrue("Expected test result", mResult.getResults().contains(caseResult));
+        assertTrue("Expected test result", mResult.getResults().contains(caseResult2));
+
+        ModuleResult otherResult = new ModuleResult(ID);
+        // Same class but all passing tests
+        ICaseResult otherCaseResult = otherResult.getOrCreateResult(CLASS);
+        otherCaseResult.getOrCreateResult(METHOD_1).passed(null);
+        otherCaseResult.getOrCreateResult(METHOD_2).passed(null);
+        otherCaseResult.getOrCreateResult(METHOD_3).passed(null);
+
+        mResult.mergeFrom(otherResult);
+
+        assertEquals("Expected two results", 2, mResult.getResults().size());
+        assertTrue("Expected test result", mResult.getResults().contains(caseResult));
+        assertTrue("Expected test result", mResult.getResults().contains(caseResult2));
+    }
+
+    public void testMergeModule_mismatchedModuleId() throws Exception {
+
+        ModuleResult otherResult = new ModuleResult(ID_2);
+        try {
+            mResult.mergeFrom(otherResult);
+            fail("Expected IlleglArgumentException");
+        } catch (IllegalArgumentException expected) {}
+    }
+}
diff --git a/common/util/tests/src/com/android/compatibility/common/util/ResultHandlerTest.java b/common/util/tests/src/com/android/compatibility/common/util/ResultHandlerTest.java
index 3009ec4..b03e8c7 100644
--- a/common/util/tests/src/com/android/compatibility/common/util/ResultHandlerTest.java
+++ b/common/util/tests/src/com/android/compatibility/common/util/ResultHandlerTest.java
@@ -80,6 +80,7 @@
     private static final String END_DISPLAY = "Fri Aug 20 15:13:04 PDT 2010";
 
     private static final String REFERENCE_URL="http://android.com";
+    private static final String COMMAND_LINE_ARGS = "cts -m CtsMyModuleTestCases";
     private static final String JOIN = "%s%s";
     private static final String XML_BASE =
             "<?xml version='1.0' encoding='UTF-8' standalone='no' ?>" +
@@ -89,7 +90,7 @@
             "suite_plan=\"%s\" suite_build_number=\"%s\" report_version=\"%s\" " +
             "devices=\"%s\" host_name=\"%s\"" +
             "os_name=\"%s\" os_version=\"%s\" os_arch=\"%s\" java_vendor=\"%s\"" +
-            "java_version=\"%s\" reference_url=\"%s\">\n" +
+            "java_version=\"%s\" reference_url=\"%s\" command_line_args=\"%s\">\n" +
             "%s%s%s" +
             "</Result>";
     private static final String XML_BUILD_INFO =
@@ -140,13 +141,13 @@
     }
 
     public void testSerialization() throws Exception {
-        IInvocationResult result = new InvocationResult(resultDir);
+        IInvocationResult result = new InvocationResult();
         result.setStartTime(START_MS);
         result.setTestPlan(SUITE_PLAN);
         result.addDeviceSerial(DEVICE_A);
         result.addDeviceSerial(DEVICE_B);
-        result.addBuildInfo(BUILD_ID, EXAMPLE_BUILD_ID);
-        result.addBuildInfo(BUILD_PRODUCT, EXAMPLE_BUILD_PRODUCT);
+        result.addInvocationInfo(BUILD_ID, EXAMPLE_BUILD_ID);
+        result.addInvocationInfo(BUILD_PRODUCT, EXAMPLE_BUILD_PRODUCT);
         IModuleResult moduleA = result.getOrCreateModule(ID_A);
         ICaseResult moduleACase = moduleA.getOrCreateResult(CLASS_A);
         ITestResult moduleATest1 = moduleACase.getOrCreateResult(METHOD_1);
@@ -170,7 +171,7 @@
 
         // Serialize to file
         ResultHandler.writeResults(SUITE_NAME, SUITE_VERSION, SUITE_PLAN, SUITE_BUILD,
-                result, resultDir, START_MS, END_MS, REFERENCE_URL);
+                result, resultDir, START_MS, END_MS, REFERENCE_URL, COMMAND_LINE_ARGS);
 
         // Parse the results and assert correctness
         checkResult(ResultHandler.getResults(resultsDir), resultDir);
@@ -211,7 +212,7 @@
             String output = String.format(XML_BASE, START_MS, END_MS, START_DISPLAY, END_DISPLAY,
                     SUITE_NAME, SUITE_VERSION, SUITE_PLAN, SUITE_BUILD, REPORT_VERSION, DEVICES,
                     hostName, OS_NAME, OS_VERSION, OS_ARCH, JAVA_VENDOR,
-                    JAVA_VERSION, REFERENCE_URL, buildInfo, summary, modules);
+                    JAVA_VERSION, REFERENCE_URL, COMMAND_LINE_ARGS, buildInfo, summary, modules);
             writer.write(output);
             writer.flush();
 
@@ -227,13 +228,11 @@
     private void checkResult(List<IInvocationResult> results, File resultDir) throws Exception {
         assertEquals("Expected 1 result", 1, results.size());
         IInvocationResult result = results.get(0);
-        assertEquals("Incorrect result dir", resultDir.getAbsolutePath(),
-                result.getResultDir().getAbsolutePath());
         assertEquals("Expected 2 passes", 2, result.countResults(TestStatus.PASS));
         assertEquals("Expected 1 failure", 1, result.countResults(TestStatus.FAIL));
         assertEquals("Expected 1 not executed", 1, result.countResults(TestStatus.NOT_EXECUTED));
 
-        Map<String, String> buildInfo = result.getBuildInfo();
+        Map<String, String> buildInfo = result.getInvocationInfo();
         assertEquals("Incorrect Build ID", EXAMPLE_BUILD_ID, buildInfo.get(BUILD_ID));
         assertEquals("Incorrect Build Product",
             EXAMPLE_BUILD_PRODUCT, buildInfo.get(BUILD_PRODUCT));
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v1-only-pkcs7-cert-bag-first-cert-not-used.apk b/hostsidetests/appsecurity/res/pkgsigverify/v1-only-pkcs7-cert-bag-first-cert-not-used.apk
new file mode 100644
index 0000000..0ebd01f
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v1-only-pkcs7-cert-bag-first-cert-not-used.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java
index 254dda8..6a47676 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java
@@ -340,6 +340,21 @@
         assertInstallFailsWithError("v2-only-missing-classes.dex.apk", "code is missing");
     }
 
+    public void testCorrectCertUsedFromPkcs7SignedDataCertsSet() throws Exception {
+        // Obtained by prepending the rsa-1024 certificate to the PKCS#7 SignedData certificates set
+        // of v1-only-with-rsa-pkcs1-sha1-2048.apk META-INF/CERT.RSA. The certs (in the order of
+        // appearance in the file) are thus: rsa-1024, rsa-2048. The package's signing cert is
+        // rsa-2048.
+        assertInstallSucceeds("v1-only-pkcs7-cert-bag-first-cert-not-used.apk");
+
+        // Check that rsa-1024 was not used as the previously installed package's signing cert.
+        assertInstallFailsWithError(
+                "v1-only-with-rsa-pkcs1-sha1-1024.apk", "signatures do not match");
+
+        // Check that rsa-2048 was used as the previously installed package's signing cert.
+        assertInstallSucceeds("v1-only-with-rsa-pkcs1-sha1-2048.apk");
+    }
+
     private void assertInstallSucceeds(String apkFilenameInResources) throws Exception {
         String installResult = installPackageFromResource(apkFilenameInResources);
         if (installResult != null) {
diff --git a/hostsidetests/appsecurity/test-apps/PrivilegedUpdateApp/CtsShimPrivUpgrade.apk b/hostsidetests/appsecurity/test-apps/PrivilegedUpdateApp/CtsShimPrivUpgrade.apk
index c71163d..18771de 100644
--- a/hostsidetests/appsecurity/test-apps/PrivilegedUpdateApp/CtsShimPrivUpgrade.apk
+++ b/hostsidetests/appsecurity/test-apps/PrivilegedUpdateApp/CtsShimPrivUpgrade.apk
Binary files differ
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/CreateAndManageUserTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/CreateAndManageUserTest.java
index 391ef15..6def711 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/CreateAndManageUserTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/CreateAndManageUserTest.java
@@ -34,240 +34,241 @@
 
 import java.lang.reflect.Field;
 
-/**
- * Test {@link DevicePolicyManager#createAndManageUser}.
- */
-public class CreateAndManageUserTest extends BaseDeviceOwnerTest {
-
-    private static final String BROADCAST_EXTRA = "broadcastExtra";
-    private static final String ACTION_EXTRA = "actionExtra";
-    private static final String SERIAL_EXTRA = "serialExtra";
-    private static final String PROFILE_OWNER_EXTRA = "profileOwnerExtra";
-    private static final String SETUP_COMPLETE_EXTRA = "setupCompleteExtra";
-    private static final int BROADCAST_TIMEOUT = 15_000;
-    private static final int USER_SWITCH_DELAY = 10_000;
-    private PackageManager mPackageManager;
-    private ActivityManager mActivityManager;
-    private volatile boolean mReceived;
-    private volatile boolean mTestProfileOwnerWasUsed;
-    private volatile boolean mSetupComplete;
-    private UserHandle mUserHandle;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mPackageManager = mContext.getPackageManager();
-        mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        mDevicePolicyManager.clearUserRestriction(getWho(), UserManager.DISALLOW_ADD_USER);
-        mDevicePolicyManager.clearUserRestriction(getWho(), UserManager.DISALLOW_REMOVE_USER);
-        // Remove user in case of failed test.
-        if (mUserHandle != null) {
-            mDevicePolicyManager.removeUser(getWho(), mUserHandle);
-            mUserHandle = null;
-        }
-        super.tearDown();
-    }
-
-    // This class is used by createAndManageUserTest as profile owner for the new user. When
-    // enabled, it sends a broadcast to signal success.
-    public static class TestProfileOwner extends DeviceAdminReceiver {
-        @Override
-        public void onEnabled(Context context, Intent intent) {
-            if (intent.getBooleanExtra(BROADCAST_EXTRA, false)) {
-                Intent i = new Intent(intent.getStringExtra(ACTION_EXTRA));
-                UserManager userManager = (UserManager)
-                        context.getSystemService(Context.USER_SERVICE);
-                long serial = intent.getLongExtra(SERIAL_EXTRA, 0);
-                UserHandle handle = userManager.getUserForSerialNumber(serial);
-                i.putExtra(PROFILE_OWNER_EXTRA, true);
-                // find value of user_setup_complete on new user, and send the result back
-                try {
-                    boolean setupComplete = (Settings.Secure.getInt(context.getContentResolver(),
-                            "user_setup_complete") == 1);
-                    i.putExtra(SETUP_COMPLETE_EXTRA, setupComplete);
-                } catch (Settings.SettingNotFoundException e) {
-                    fail("Did not find settings user_setup_complete");
-                }
-
-                context.sendBroadcastAsUser(i, handle);
-            }
-        }
-
-        public static ComponentName getComponentName() {
-            return new ComponentName(CreateAndManageUserTest.class.getPackage().getName(),
-                    TestProfileOwner.class.getName());
-        }
-    }
-
-    private void waitForBroadcastLocked() {
-        // Wait for broadcast. Time is measured in a while loop because of spurious wakeups.
-        final long initTime = System.currentTimeMillis();
-        while (!mReceived) {
-            try {
-                wait(BROADCAST_TIMEOUT - (System.currentTimeMillis() - initTime));
-            } catch (InterruptedException e) {
-                fail("InterruptedException: " + e.getMessage());
-            }
-            if (!mReceived && System.currentTimeMillis() - initTime > BROADCAST_TIMEOUT) {
-                fail("Timeout while waiting for broadcast after createAndManageUser.");
-            }
-        }
-    }
-
-    // This test will create a user that will get passed a bundle that we specify. The bundle will
-    // contain an action and a serial (for user handle) to broadcast to notify the test that the
-    // configuration was triggered.
-    private void createAndManageUserTest(final int flags) {
-        // This test sets a profile owner on the user, which requires the managed_users feature.
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS)) {
-            return;
-        }
-
-        final boolean expectedSetupComplete = (flags & DevicePolicyManager.SKIP_SETUP_WIZARD) != 0;
-        UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-
-        UserHandle firstUser = Process.myUserHandle();
-        final String testUserName = "TestUser_" + System.currentTimeMillis();
-        String action = "com.android.cts.TEST_USER_ACTION";
-        PersistableBundle bundle = new PersistableBundle();
-        bundle.putBoolean(BROADCAST_EXTRA, true);
-        bundle.putLong(SERIAL_EXTRA, userManager.getSerialNumberForUser(firstUser));
-        bundle.putString(ACTION_EXTRA, action);
-
-        mReceived = false;
-        mTestProfileOwnerWasUsed = false;
-        mSetupComplete = !expectedSetupComplete;
-        BroadcastReceiver receiver = new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                mReceived = true;
-                if (intent.getBooleanExtra(PROFILE_OWNER_EXTRA, false)) {
-                    mTestProfileOwnerWasUsed = true;
-                }
-                mSetupComplete = intent.getBooleanExtra(SETUP_COMPLETE_EXTRA,
-                        !expectedSetupComplete);
-                synchronized (CreateAndManageUserTest.this) {
-                    CreateAndManageUserTest.this.notify();
-                }
-            }
-        };
-
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(action);
-        mContext.registerReceiver(receiver, filter);
-
-        synchronized (this) {
-            mUserHandle = mDevicePolicyManager.createAndManageUser(getWho(), testUserName,
-                    TestProfileOwner.getComponentName(), bundle, flags);
-            assertNotNull(mUserHandle);
-
-            mDevicePolicyManager.switchUser(getWho(), mUserHandle);
-            try {
-                wait(USER_SWITCH_DELAY);
-            } catch (InterruptedException e) {
-                fail("InterruptedException: " + e.getMessage());
-            }
-            mDevicePolicyManager.switchUser(getWho(), firstUser);
-
-            waitForBroadcastLocked();
-
-            assertTrue(mReceived);
-            assertTrue(mTestProfileOwnerWasUsed);
-            assertEquals(expectedSetupComplete, mSetupComplete);
-
-            assertTrue(mDevicePolicyManager.removeUser(getWho(), mUserHandle));
-
-            mUserHandle = null;
-        }
-
-        mContext.unregisterReceiver(receiver);
-    }
-
-    /**
-     * Test creating an ephemeral user using the {@link DevicePolicyManager#createAndManageUser}
-     * method.
-     *
-     * <p>The test creates a user by calling to {@link DevicePolicyManager#createAndManageUser}. It
-     * doesn't remove the user afterwards, so its properties can be queried and tested by host-side
-     * tests.
-     * <p>The user's flags will be checked from the corresponding host-side test.
-     */
-    public void testCreateAndManageEphemeralUser() throws Exception {
-        String testUserName = "TestUser_" + System.currentTimeMillis();
-
-        // Use reflection to get the value of the hidden flag to make the new user ephemeral.
-        Field field = DevicePolicyManager.class.getField("MAKE_USER_EPHEMERAL");
-        int makeEphemeralFlag = field.getInt(null);
-
-        // Do not assign return value to mUserHandle, so it is not removed in tearDown.
-        mDevicePolicyManager.createAndManageUser(
-                getWho(),
-                testUserName,
-                getWho(),
-                null,
-                makeEphemeralFlag);
-    }
-
-    /**
-     * Test creating an ephemeral user using the {@link DevicePolicyManager#createAndManageUser}
-     * method fails on systems without the split system user.
-     *
-     * <p>To be used by host-side test on systems without the split system user.
-     */
-    public void testCreateAndManageEphemeralUserFails() throws Exception {
-        String testUserName = "TestUser_" + System.currentTimeMillis();
-
-        // Use reflection to get the value of the hidden flag to make the new user ephemeral.
-        Field field = DevicePolicyManager.class.getField("MAKE_USER_EPHEMERAL");
-        int makeEphemeralFlag = field.getInt(null);
-
-        try {
-            mDevicePolicyManager.createAndManageUser(
-                    getWho(),
-                    testUserName,
-                    getWho(),
-                    null,
-                    makeEphemeralFlag);
-        } catch (IllegalArgumentException e) {
-            // Success, the expected exception was thrown.
-            return;
-        }
-        fail("createAndManageUser should have thrown IllegalArgumentException");
-    }
-
-    public void testCreateAndManageUser_SkipSetupWizard() {
-        createAndManageUserTest(DevicePolicyManager.SKIP_SETUP_WIZARD);
-    }
-
-    public void testCreateAndManageUser_DontSkipSetupWizard() {
-        if (!mActivityManager.isRunningInTestHarness()) {
-            // In test harness, the setup wizard will be disabled by default, so this test is always
-            // failing.
-            createAndManageUserTest(0);
-        }
-    }
-
-    // createAndManageUser should circumvent the DISALLOW_ADD_USER restriction
-    public void testCreateAndManageUser_AddRestrictionSet() {
-        mDevicePolicyManager.addUserRestriction(getWho(), UserManager.DISALLOW_ADD_USER);
-
-        mUserHandle = mDevicePolicyManager.createAndManageUser(getWho(), "Test User", getWho(),
-                null, 0);
-        assertNotNull(mUserHandle);
-    }
-
-    public void testCreateAndManageUser_RemoveRestrictionSet() {
-        mDevicePolicyManager.addUserRestriction(getWho(), UserManager.DISALLOW_REMOVE_USER);
-
-        mUserHandle = mDevicePolicyManager.createAndManageUser(getWho(), "Test User", getWho(),
-                null, 0);
-        assertNotNull(mUserHandle);
-
-        boolean removed = mDevicePolicyManager.removeUser(getWho(), mUserHandle);
-        assertFalse(removed);
-    }
-}
+// Disabled due to b/29072728
+///**
+// * Test {@link DevicePolicyManager#createAndManageUser}.
+// */
+//public class CreateAndManageUserTest extends BaseDeviceOwnerTest {
+//
+//    private static final String BROADCAST_EXTRA = "broadcastExtra";
+//    private static final String ACTION_EXTRA = "actionExtra";
+//    private static final String SERIAL_EXTRA = "serialExtra";
+//    private static final String PROFILE_OWNER_EXTRA = "profileOwnerExtra";
+//    private static final String SETUP_COMPLETE_EXTRA = "setupCompleteExtra";
+//    private static final int BROADCAST_TIMEOUT = 15_000;
+//    private static final int USER_SWITCH_DELAY = 10_000;
+//    private PackageManager mPackageManager;
+//    private ActivityManager mActivityManager;
+//    private volatile boolean mReceived;
+//    private volatile boolean mTestProfileOwnerWasUsed;
+//    private volatile boolean mSetupComplete;
+//    private UserHandle mUserHandle;
+//
+//    @Override
+//    protected void setUp() throws Exception {
+//        super.setUp();
+//        mPackageManager = mContext.getPackageManager();
+//        mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+//    }
+//
+//    @Override
+//    protected void tearDown() throws Exception {
+//        mDevicePolicyManager.clearUserRestriction(getWho(), UserManager.DISALLOW_ADD_USER);
+//        mDevicePolicyManager.clearUserRestriction(getWho(), UserManager.DISALLOW_REMOVE_USER);
+//        // Remove user in case of failed test.
+//        if (mUserHandle != null) {
+//            mDevicePolicyManager.removeUser(getWho(), mUserHandle);
+//            mUserHandle = null;
+//        }
+//        super.tearDown();
+//    }
+//
+//    // This class is used by createAndManageUserTest as profile owner for the new user. When
+//    // enabled, it sends a broadcast to signal success.
+//    public static class TestProfileOwner extends DeviceAdminReceiver {
+//        @Override
+//        public void onEnabled(Context context, Intent intent) {
+//            if (intent.getBooleanExtra(BROADCAST_EXTRA, false)) {
+//                Intent i = new Intent(intent.getStringExtra(ACTION_EXTRA));
+//                UserManager userManager = (UserManager)
+//                        context.getSystemService(Context.USER_SERVICE);
+//                long serial = intent.getLongExtra(SERIAL_EXTRA, 0);
+//                UserHandle handle = userManager.getUserForSerialNumber(serial);
+//                i.putExtra(PROFILE_OWNER_EXTRA, true);
+//                // find value of user_setup_complete on new user, and send the result back
+//                try {
+//                    boolean setupComplete = (Settings.Secure.getInt(context.getContentResolver(),
+//                            "user_setup_complete") == 1);
+//                    i.putExtra(SETUP_COMPLETE_EXTRA, setupComplete);
+//                } catch (Settings.SettingNotFoundException e) {
+//                    fail("Did not find settings user_setup_complete");
+//                }
+//
+//                context.sendBroadcastAsUser(i, handle);
+//            }
+//        }
+//
+//        public static ComponentName getComponentName() {
+//            return new ComponentName(CreateAndManageUserTest.class.getPackage().getName(),
+//                    TestProfileOwner.class.getName());
+//        }
+//    }
+//
+//    private void waitForBroadcastLocked() {
+//        // Wait for broadcast. Time is measured in a while loop because of spurious wakeups.
+//        final long initTime = System.currentTimeMillis();
+//        while (!mReceived) {
+//            try {
+//                wait(BROADCAST_TIMEOUT - (System.currentTimeMillis() - initTime));
+//            } catch (InterruptedException e) {
+//                fail("InterruptedException: " + e.getMessage());
+//            }
+//            if (!mReceived && System.currentTimeMillis() - initTime > BROADCAST_TIMEOUT) {
+//                fail("Timeout while waiting for broadcast after createAndManageUser.");
+//            }
+//        }
+//    }
+//
+//    // This test will create a user that will get passed a bundle that we specify. The bundle will
+//    // contain an action and a serial (for user handle) to broadcast to notify the test that the
+//    // configuration was triggered.
+//    private void createAndManageUserTest(final int flags) {
+//        // This test sets a profile owner on the user, which requires the managed_users feature.
+//        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS)) {
+//            return;
+//        }
+//
+//        final boolean expectedSetupComplete = (flags & DevicePolicyManager.SKIP_SETUP_WIZARD) != 0;
+//        UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+//
+//        UserHandle firstUser = Process.myUserHandle();
+//        final String testUserName = "TestUser_" + System.currentTimeMillis();
+//        String action = "com.android.cts.TEST_USER_ACTION";
+//        PersistableBundle bundle = new PersistableBundle();
+//        bundle.putBoolean(BROADCAST_EXTRA, true);
+//        bundle.putLong(SERIAL_EXTRA, userManager.getSerialNumberForUser(firstUser));
+//        bundle.putString(ACTION_EXTRA, action);
+//
+//        mReceived = false;
+//        mTestProfileOwnerWasUsed = false;
+//        mSetupComplete = !expectedSetupComplete;
+//        BroadcastReceiver receiver = new BroadcastReceiver() {
+//            @Override
+//            public void onReceive(Context context, Intent intent) {
+//                mReceived = true;
+//                if (intent.getBooleanExtra(PROFILE_OWNER_EXTRA, false)) {
+//                    mTestProfileOwnerWasUsed = true;
+//                }
+//                mSetupComplete = intent.getBooleanExtra(SETUP_COMPLETE_EXTRA,
+//                        !expectedSetupComplete);
+//                synchronized (CreateAndManageUserTest.this) {
+//                    CreateAndManageUserTest.this.notify();
+//                }
+//            }
+//        };
+//
+//        IntentFilter filter = new IntentFilter();
+//        filter.addAction(action);
+//        mContext.registerReceiver(receiver, filter);
+//
+//        synchronized (this) {
+//            mUserHandle = mDevicePolicyManager.createAndManageUser(getWho(), testUserName,
+//                    TestProfileOwner.getComponentName(), bundle, flags);
+//            assertNotNull(mUserHandle);
+//
+//            mDevicePolicyManager.switchUser(getWho(), mUserHandle);
+//            try {
+//                wait(USER_SWITCH_DELAY);
+//            } catch (InterruptedException e) {
+//                fail("InterruptedException: " + e.getMessage());
+//            }
+//            mDevicePolicyManager.switchUser(getWho(), firstUser);
+//
+//            waitForBroadcastLocked();
+//
+//            assertTrue(mReceived);
+//            assertTrue(mTestProfileOwnerWasUsed);
+//            assertEquals(expectedSetupComplete, mSetupComplete);
+//
+//            assertTrue(mDevicePolicyManager.removeUser(getWho(), mUserHandle));
+//
+//            mUserHandle = null;
+//        }
+//
+//        mContext.unregisterReceiver(receiver);
+//    }
+//
+//    /**
+//     * Test creating an ephemeral user using the {@link DevicePolicyManager#createAndManageUser}
+//     * method.
+//     *
+//     * <p>The test creates a user by calling to {@link DevicePolicyManager#createAndManageUser}. It
+//     * doesn't remove the user afterwards, so its properties can be queried and tested by host-side
+//     * tests.
+//     * <p>The user's flags will be checked from the corresponding host-side test.
+//     */
+//    public void testCreateAndManageEphemeralUser() throws Exception {
+//        String testUserName = "TestUser_" + System.currentTimeMillis();
+//
+//        // Use reflection to get the value of the hidden flag to make the new user ephemeral.
+//        Field field = DevicePolicyManager.class.getField("MAKE_USER_EPHEMERAL");
+//        int makeEphemeralFlag = field.getInt(null);
+//
+//        // Do not assign return value to mUserHandle, so it is not removed in tearDown.
+//        mDevicePolicyManager.createAndManageUser(
+//                getWho(),
+//                testUserName,
+//                getWho(),
+//                null,
+//                makeEphemeralFlag);
+//    }
+//
+//    /**
+//     * Test creating an ephemeral user using the {@link DevicePolicyManager#createAndManageUser}
+//     * method fails on systems without the split system user.
+//     *
+//     * <p>To be used by host-side test on systems without the split system user.
+//     */
+//    public void testCreateAndManageEphemeralUserFails() throws Exception {
+//        String testUserName = "TestUser_" + System.currentTimeMillis();
+//
+//        // Use reflection to get the value of the hidden flag to make the new user ephemeral.
+//        Field field = DevicePolicyManager.class.getField("MAKE_USER_EPHEMERAL");
+//        int makeEphemeralFlag = field.getInt(null);
+//
+//        try {
+//            mDevicePolicyManager.createAndManageUser(
+//                    getWho(),
+//                    testUserName,
+//                    getWho(),
+//                    null,
+//                    makeEphemeralFlag);
+//        } catch (IllegalArgumentException e) {
+//            // Success, the expected exception was thrown.
+//            return;
+//        }
+//        fail("createAndManageUser should have thrown IllegalArgumentException");
+//    }
+//
+//    public void testCreateAndManageUser_SkipSetupWizard() {
+//        createAndManageUserTest(DevicePolicyManager.SKIP_SETUP_WIZARD);
+//    }
+//
+//    public void testCreateAndManageUser_DontSkipSetupWizard() {
+//        if (!mActivityManager.isRunningInTestHarness()) {
+//            // In test harness, the setup wizard will be disabled by default, so this test is always
+//            // failing.
+//            createAndManageUserTest(0);
+//        }
+//    }
+//
+//    // createAndManageUser should circumvent the DISALLOW_ADD_USER restriction
+//    public void testCreateAndManageUser_AddRestrictionSet() {
+//        mDevicePolicyManager.addUserRestriction(getWho(), UserManager.DISALLOW_ADD_USER);
+//
+//        mUserHandle = mDevicePolicyManager.createAndManageUser(getWho(), "Test User", getWho(),
+//                null, 0);
+//        assertNotNull(mUserHandle);
+//    }
+//
+//    public void testCreateAndManageUser_RemoveRestrictionSet() {
+//        mDevicePolicyManager.addUserRestriction(getWho(), UserManager.DISALLOW_REMOVE_USER);
+//
+//        mUserHandle = mDevicePolicyManager.createAndManageUser(getWho(), "Test User", getWho(),
+//                null, 0);
+//        assertNotNull(mUserHandle);
+//
+//        boolean removed = mDevicePolicyManager.removeUser(getWho(), mUserHandle);
+//        assertFalse(removed);
+//    }
+//}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
index 39d7f22..c7a84cf 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
@@ -215,75 +215,76 @@
         assertTrue("User must be ephemeral", 0 != (getUserFlags(userId) & FLAG_EPHEMERAL));
     }
 
-    /**
-     * Test creating an epehemeral user using the DevicePolicyManager's createAndManageUser method.
-     */
-    public void testCreateAndManageEphemeralUser() throws Exception {
-        if (!mHasEphemeralUserFeature) {
-            return;
-        }
-
-        ArrayList<Integer> originalUsers = listUsers();
-        executeDeviceTestMethod(".CreateAndManageUserTest", "testCreateAndManageEphemeralUser");
-
-        ArrayList<Integer> newUsers = listUsers();
-
-        // Check that exactly one new user was created.
-        assertEquals(
-                "One user should have been created", originalUsers.size() + 1, newUsers.size());
-
-        // Get the id of the newly created user.
-        int newUserId = -1;
-        for (int userId : newUsers) {
-            if (!originalUsers.contains(userId)) {
-                newUserId = userId;
-                break;
-            }
-        }
-
-        // Get the flags of the new user and check the user is ephemeral.
-        int flags = getUserFlags(newUserId);
-        assertEquals("Ephemeral flag must be set", FLAG_EPHEMERAL, flags & FLAG_EPHEMERAL);
-    }
-
-    /**
-     * Test that creating an epehemeral user using the DevicePolicyManager's createAndManageUser
-     * method fails on systems without the split system user.
-     */
-    public void testCreateAndManageEphemeralUserFailsWithoutSplitSystemUser() throws Exception {
-        if (mHasDisabledEphemeralUserFeature) {
-            executeDeviceTestMethod(
-                    ".CreateAndManageUserTest", "testCreateAndManageEphemeralUserFails");
-        }
-    }
-
-    public void testCreateAndManageUser_SkipSetupWizard() throws Exception {
-        if (mHasCreateAndManageUserFeature) {
-            executeDeviceTestMethod(".CreateAndManageUserTest",
-                "testCreateAndManageUser_SkipSetupWizard");
-        }
-    }
-
-    public void testCreateAndManageUser_DontSkipSetupWizard() throws Exception {
-        if (mHasCreateAndManageUserFeature) {
-            executeDeviceTestMethod(".CreateAndManageUserTest",
-                "testCreateAndManageUser_DontSkipSetupWizard");
-        }
-    }
-
-    public void testCreateAndManageUser_AddRestrictionSet() throws Exception {
-        if (mHasCreateAndManageUserFeature) {
-            executeDeviceTestMethod(".CreateAndManageUserTest",
-                "testCreateAndManageUser_AddRestrictionSet");
-        }
-    }
-
-    public void testCreateAndManageUser_RemoveRestrictionSet() throws Exception {
-        if (mHasCreateAndManageUserFeature) {
-            executeDeviceTestMethod(".CreateAndManageUserTest",
-                "testCreateAndManageUser_RemoveRestrictionSet");
-        }
-    }
+// Disabled due to b/29072728
+//    /**
+//     * Test creating an epehemeral user using the DevicePolicyManager's createAndManageUser method.
+//     */
+//    public void testCreateAndManageEphemeralUser() throws Exception {
+//        if (!mHasEphemeralUserFeature) {
+//            return;
+//        }
+//
+//        ArrayList<Integer> originalUsers = listUsers();
+//        executeDeviceTestMethod(".CreateAndManageUserTest", "testCreateAndManageEphemeralUser");
+//
+//        ArrayList<Integer> newUsers = listUsers();
+//
+//        // Check that exactly one new user was created.
+//        assertEquals(
+//                "One user should have been created", originalUsers.size() + 1, newUsers.size());
+//
+//        // Get the id of the newly created user.
+//        int newUserId = -1;
+//        for (int userId : newUsers) {
+//            if (!originalUsers.contains(userId)) {
+//                newUserId = userId;
+//                break;
+//            }
+//        }
+//
+//        // Get the flags of the new user and check the user is ephemeral.
+//        int flags = getUserFlags(newUserId);
+//        assertEquals("Ephemeral flag must be set", FLAG_EPHEMERAL, flags & FLAG_EPHEMERAL);
+//    }
+//
+//    /**
+//     * Test that creating an epehemeral user using the DevicePolicyManager's createAndManageUser
+//     * method fails on systems without the split system user.
+//     */
+//    public void testCreateAndManageEphemeralUserFailsWithoutSplitSystemUser() throws Exception {
+//        if (mHasDisabledEphemeralUserFeature) {
+//            executeDeviceTestMethod(
+//                    ".CreateAndManageUserTest", "testCreateAndManageEphemeralUserFails");
+//        }
+//    }
+//
+//    public void testCreateAndManageUser_SkipSetupWizard() throws Exception {
+//        if (mHasCreateAndManageUserFeature) {
+//            executeDeviceTestMethod(".CreateAndManageUserTest",
+//                "testCreateAndManageUser_SkipSetupWizard");
+//        }
+//    }
+//
+//    public void testCreateAndManageUser_DontSkipSetupWizard() throws Exception {
+//        if (mHasCreateAndManageUserFeature) {
+//            executeDeviceTestMethod(".CreateAndManageUserTest",
+//                "testCreateAndManageUser_DontSkipSetupWizard");
+//        }
+//    }
+//
+//    public void testCreateAndManageUser_AddRestrictionSet() throws Exception {
+//        if (mHasCreateAndManageUserFeature) {
+//            executeDeviceTestMethod(".CreateAndManageUserTest",
+//                "testCreateAndManageUser_AddRestrictionSet");
+//        }
+//    }
+//
+//    public void testCreateAndManageUser_RemoveRestrictionSet() throws Exception {
+//        if (mHasCreateAndManageUserFeature) {
+//            executeDeviceTestMethod(".CreateAndManageUserTest",
+//                "testCreateAndManageUser_RemoveRestrictionSet");
+//        }
+//    }
 
     public void testSecurityLoggingWithTwoUsers() throws Exception {
         if (!mHasFeature || getMaxNumberOfUsersSupported() < 2) {
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractAppIdleTestCase.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractAppIdleTestCase.java
index 13ce6ce..ba56665 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractAppIdleTestCase.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractAppIdleTestCase.java
@@ -16,6 +16,8 @@
 
 package com.android.cts.net.hostside;
 
+import android.util.Log;
+
 /**
  * Base class for metered and non-metered tests on idle apps.
  */
@@ -25,6 +27,8 @@
     protected final void setUp() throws Exception {
         super.setUp();
 
+        if (!isSupported()) return;
+
         // Set initial state.
         setUpMeteredNetwork();
         removePowerSaveModeWhitelist(TEST_APP2_PKG);
@@ -38,6 +42,8 @@
     protected final void tearDown() throws Exception {
         super.tearDown();
 
+        if (!isSupported()) return;
+
         try {
             tearDownMeteredNetwork();
         } finally {
@@ -46,6 +52,16 @@
         }
     }
 
+    @Override
+    protected boolean isSupported() throws Exception {
+        boolean supported = isDozeModeEnabled();
+        if (!supported) {
+            Log.i(TAG, "Skipping " + getClass() + "." + getName()
+                    + "() because device does not support Doze Mode");
+        }
+        return supported;
+    }
+
     /**
      * Sets the initial (non) metered network state.
      *
@@ -63,6 +79,8 @@
     }
 
     public void testBackgroundNetworkAccess_enabled() throws Exception {
+        if (!isSupported()) return;
+
         setAppIdle(true);
         assertBackgroundNetworkAccess(false);
 
@@ -92,6 +110,8 @@
     }
 
     public void testBackgroundNetworkAccess_whitelisted() throws Exception {
+        if (!isSupported()) return;
+
         setAppIdle(true);
         assertBackgroundNetworkAccess(false);
 
@@ -111,6 +131,8 @@
     }
 
     public void testBackgroundNetworkAccess_disabled() throws Exception {
+        if (!isSupported()) return;
+
         assertBackgroundNetworkAccess(true);
 
         assertsForegroundAlwaysHasNetworkAccess();
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractBatterySaverModeTestCase.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractBatterySaverModeTestCase.java
index 2acc670..c1c91da 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractBatterySaverModeTestCase.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractBatterySaverModeTestCase.java
@@ -16,6 +16,8 @@
 
 package com.android.cts.net.hostside;
 
+import android.util.Log;
+
 /**
  * Base class for metered and non-metered Battery Saver Mode tests.
  */
@@ -25,6 +27,8 @@
     protected final void setUp() throws Exception {
         super.setUp();
 
+        if (!isSupported()) return;
+
         // Set initial state.
         setUpMeteredNetwork();
         removePowerSaveModeWhitelist(TEST_APP2_PKG);
@@ -37,6 +41,8 @@
     protected final void tearDown() throws Exception {
         super.tearDown();
 
+        if (!isSupported()) return;
+
         try {
             tearDownMeteredNetwork();
         } finally {
@@ -44,6 +50,16 @@
         }
     }
 
+    @Override
+    protected boolean isSupported() throws Exception {
+        boolean supported = isDozeModeEnabled();
+        if (!supported) {
+            Log.i(TAG, "Skipping " + getClass() + "." + getName()
+                    + "() because device does not support Doze Mode");
+        }
+        return supported;
+    }
+
     /**
      * Sets the initial (non) metered network state.
      *
@@ -61,6 +77,8 @@
     }
 
     public void testBackgroundNetworkAccess_enabled() throws Exception {
+        if (!isSupported()) return;
+
         setBatterySaverMode(true);
         assertBackgroundNetworkAccess(false);
 
@@ -87,6 +105,8 @@
     }
 
     public void testBackgroundNetworkAccess_whitelisted() throws Exception {
+        if (!isSupported()) return;
+
         setBatterySaverMode(true);
         assertBackgroundNetworkAccess(false);
 
@@ -101,6 +121,8 @@
     }
 
     public void testBackgroundNetworkAccess_disabled() throws Exception {
+        if (!isSupported()) return;
+
         assertBackgroundNetworkAccess(true);
 
         assertsForegroundAlwaysHasNetworkAccess();
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java
index e0ba76b8..b89cf93 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java
@@ -17,6 +17,7 @@
 package com.android.cts.net.hostside;
 
 import android.os.SystemClock;
+import android.util.Log;
 
 /**
  * Base class for metered and non-metered Doze Mode tests.
@@ -27,6 +28,8 @@
     protected final void setUp() throws Exception {
         super.setUp();
 
+        if (!isSupported()) return;
+
         // Set initial state.
         setUpMeteredNetwork();
         removePowerSaveModeWhitelist(TEST_APP2_PKG);
@@ -39,6 +42,8 @@
     protected final void tearDown() throws Exception {
         super.tearDown();
 
+        if (!isSupported()) return;
+
         try {
             tearDownMeteredNetwork();
         } finally {
@@ -46,6 +51,16 @@
         }
     }
 
+    @Override
+    protected boolean isSupported() throws Exception {
+        boolean supported = isDozeModeEnabled();
+        if (!supported) {
+            Log.i(TAG, "Skipping " + getClass() + "." + getName()
+                    + "() because device does not support Doze Mode");
+        }
+        return supported;
+    }
+
     /**
      * Sets the initial (non) metered network state.
      *
@@ -63,6 +78,8 @@
     }
 
     public void testBackgroundNetworkAccess_enabled() throws Exception {
+        if (!isSupported()) return;
+
         setDozeMode(true);
         assertBackgroundNetworkAccess(false);
 
@@ -81,6 +98,8 @@
     }
 
     public void testBackgroundNetworkAccess_whitelisted() throws Exception {
+        if (!isSupported()) return;
+
         setDozeMode(true);
         assertBackgroundNetworkAccess(false);
 
@@ -95,6 +114,8 @@
     }
 
     public void testBackgroundNetworkAccess_disabled() throws Exception {
+        if (!isSupported()) return;
+
         assertBackgroundNetworkAccess(true);
 
         assertsForegroundAlwaysHasNetworkAccess();
@@ -103,6 +124,8 @@
 
     public void testBackgroundNetworkAccess_enabledButWhitelistedOnNotificationAction()
             throws Exception {
+        if (!isSupported()) return;
+
         setPendingIntentWhitelistDuration(NETWORK_TIMEOUT_MS);
         try {
             registerNotificationListenerService();
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index ba383a8..439fbbe 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -188,6 +188,22 @@
     }
 
     /**
+     * Whether this device suport this type of test.
+     *
+     * <p>Should be overridden when necessary, and explicitly used before each test. Example:
+     *
+     * <pre><code>
+     * public void testSomething() {
+     *    if (!isSupported()) return;
+     * </code></pre>
+     *
+     * @return {@code true} by default.
+     */
+    protected boolean isSupported() throws Exception {
+        return true;
+    }
+
+    /**
      * Asserts that an app always have access while on foreground or running a foreground service.
      *
      * <p>This method will launch an activity and a foreground service to make the assertion, but
@@ -269,8 +285,6 @@
     private void assertNetworkAccess(boolean expectAvailable) throws Exception {
         final Intent intent = new Intent(ACTION_CHECK_NETWORK);
 
-        // When the network info state change, it's possible the app still get the previous value,
-        // so we need to retry a couple times.
         final int maxTries = 5;
         String resultData = null;
         for (int i = 1; i <= maxTries; i++) {
@@ -287,8 +301,14 @@
             final String networkInfo = parts[4];
 
             if (expectAvailable) {
-                assertTrue("should be connected: " + connectionCheckDetails
-                        + " (network info: " + networkInfo + ")", connected);
+                if (!connected) {
+                    // Since it's establishing a connection to an external site, it could be flaky.
+                    Log.w(TAG, "Failed to connect to an external site on attempt #" + i +
+                            " (error: " + connectionCheckDetails + ", NetworkInfo: " + networkInfo
+                            + "); sleeping " + NETWORK_TIMEOUT_MS + "ms before trying again");
+                    SystemClock.sleep(NETWORK_TIMEOUT_MS);
+                    continue;
+                }
                 if (state != State.CONNECTED) {
                     Log.d(TAG, "State (" + state + ") not set to CONNECTED on attempt #" + i
                             + "; sleeping 1s before trying again");
@@ -303,6 +323,8 @@
                 assertFalse("should not be connected: " + connectionCheckDetails
                         + " (network info: " + networkInfo + ")", connected);
                 if (state != State.DISCONNECTED) {
+                    // When the network info state change, it's possible the app still get the
+                    // previous value, so we need to retry a couple times.
                     Log.d(TAG, "State (" + state + ") not set to DISCONNECTED on attempt #" + i
                             + "; sleeping 1s before trying again");
                     SystemClock.sleep(SECOND_IN_MS);
@@ -313,7 +335,8 @@
                 }
             }
         }
-        fail("Invalid state after " + maxTries + " attempts. Last data: " + resultData);
+        fail("Invalid state for expectAvailable=" + expectAvailable + " after " + maxTries
+                + " attempts. Last data: " + resultData);
     }
 
     protected String executeShellCommand(String command) throws Exception {
@@ -591,6 +614,9 @@
     }
 
     protected void setDozeMode(boolean enabled) throws Exception {
+        // Sanity check, since tests should check beforehand....
+        assertTrue("Device does not support Doze Mode", isDozeModeEnabled());
+
         Log.i(TAG, "Setting Doze Mode to " + enabled);
         if (enabled) {
             turnBatteryOff();
@@ -609,6 +635,11 @@
         assertDelayedShellCommand("dumpsys deviceidle get deep", enabled ? "IDLE" : "ACTIVE");
     }
 
+    protected boolean isDozeModeEnabled() throws Exception {
+        final String result = executeShellCommand("cmd deviceidle enabled deep").trim();
+        return result.equals("1");
+    }
+
     protected void setAppIdle(boolean enabled) throws Exception {
         Log.i(TAG, "Setting app idle to " + enabled);
         executeSilentShellCommand("am set-inactive " + TEST_APP2_PKG + " " + enabled );
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/DataSaverModeTest.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
index 1895156..3e6bd33 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
@@ -30,6 +30,8 @@
     public void setUp() throws Exception {
         super.setUp();
 
+        if (!isSupported()) return;
+
         // Set initial state.
         setMeteredNetwork();
         setRestrictBackground(false);
@@ -44,6 +46,8 @@
     protected void tearDown() throws Exception {
         super.tearDown();
 
+        if (!isSupported()) return;
+
         try {
             resetMeteredNetwork();
         } finally {
@@ -52,6 +56,8 @@
     }
 
     public void testGetRestrictBackgroundStatus_disabled() throws Exception {
+        if (!isSupported()) return;
+
         assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_DISABLED);
 
         // Sanity check: make sure status is always disabled, never whitelisted
@@ -64,6 +70,8 @@
     }
 
     public void testGetRestrictBackgroundStatus_whitelisted() throws Exception {
+        if (!isSupported()) return;
+
         setRestrictBackground(true);
         assertRestrictBackgroundChangedReceived(1);
         assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
@@ -81,6 +89,8 @@
     }
 
     public void testGetRestrictBackgroundStatus_enabled() throws Exception {
+        if (!isSupported()) return;
+
         setRestrictBackground(true);
         assertRestrictBackgroundChangedReceived(1);
         assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
@@ -108,6 +118,8 @@
     }
 
     public void testGetRestrictBackgroundStatus_blacklisted() throws Exception {
+        if (!isSupported()) return;
+
         addRestrictBackgroundBlacklist(mUid);
         assertRestrictBackgroundChangedReceived(1);
         assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
@@ -136,6 +148,8 @@
     }
 
     public void testGetRestrictBackgroundStatus_requiredWhitelistedPackages() throws Exception {
+        if (!isSupported()) return;
+
         final StringBuilder error = new StringBuilder();
         for (String packageName : REQUIRED_WHITELISTED_PACKAGES) {
             int uid = -1;
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/MixedModesTest.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/MixedModesTest.java
index c97a0f9..af52eee 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/MixedModesTest.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/MixedModesTest.java
@@ -32,6 +32,8 @@
     public void setUp() throws Exception {
         super.setUp();
 
+        if (!isSupported()) return;
+
         // Set initial state.
         removeRestrictBackgroundWhitelist(mUid);
         removeRestrictBackgroundBlacklist(mUid);
@@ -44,6 +46,8 @@
     protected void tearDown() throws Exception {
         super.tearDown();
 
+        if (!isSupported()) return;
+
         try {
             setRestrictBackground(false);
         } finally {
@@ -55,6 +59,14 @@
      * Tests all DS ON and BS ON scenarios from network-policy-restrictions.md on metered networks.
      */
     public void testDataAndBatterySaverModes_meteredNetwork() throws Exception {
+        if (!isSupported()) return;
+
+        if (!isDozeModeEnabled()) {
+            Log.w(TAG, "testDataAndBatterySaverModes_meteredNetwork() skipped because "
+                    + "device does not support Doze Mode");
+            return;
+        }
+
         Log.i(TAG, "testDataAndBatterySaverModes_meteredNetwork() tests");
         setMeteredNetwork();
 
@@ -119,6 +131,14 @@
      * networks.
      */
     public void testDataAndBatterySaverModes_nonMeteredNetwork() throws Exception {
+        if (!isSupported()) return;
+
+        if (!isDozeModeEnabled()) {
+            Log.w(TAG, "testDataAndBatterySaverModes_nonMeteredNetwork() skipped because "
+                    + "device does not support Doze Mode");
+            return;
+        }
+
         if (mCm.isActiveNetworkMetered()) {
             Log.w(TAG, "testDataAndBatterySaverModes_nonMeteredNetwork() skipped because network"
                     + " is metered");
diff --git a/hostsidetests/net/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java b/hostsidetests/net/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
index c741b12..7d5f817 100644
--- a/hostsidetests/net/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
+++ b/hostsidetests/net/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
@@ -101,6 +101,12 @@
     }
 
     public void testBatterySaverMode_reinstall() throws Exception {
+        if (!isDozeModeEnabled()) {
+            Log.w(TAG, "testBatterySaverMode_reinstall() skipped because device does not support "
+                    + "Doze Mode");
+            return;
+        }
+
         addPowerSaveModeWhitelist(TEST_APP2_PKG);
 
         uninstallPackage(TEST_APP2_PKG, true);
@@ -287,4 +293,9 @@
         runCommand("dumpsys deviceidle whitelist +" + packageName);
         assertPowerSaveModeWhitelist(packageName, true); // Sanity check
     }
+
+    protected boolean isDozeModeEnabled() throws Exception {
+        final String result = runCommand("cmd deviceidle enabled deep").trim();
+        return result.equals("1");
+    }
 }
diff --git a/hostsidetests/numberblocking/src/com/android/cts/numberblocking/hostside/NumberBlockingTest.java b/hostsidetests/numberblocking/src/com/android/cts/numberblocking/hostside/NumberBlockingTest.java
index eae6d86..11455a0 100644
--- a/hostsidetests/numberblocking/src/com/android/cts/numberblocking/hostside/NumberBlockingTest.java
+++ b/hostsidetests/numberblocking/src/com/android/cts/numberblocking/hostside/NumberBlockingTest.java
@@ -45,18 +45,25 @@
     private static final String NUMBER_BLOCKING_APP_TEST_CLASS_NAME = "NumberBlockingAppTest";
     private static final String TEST_APP_CONNECTION_SERVICE_NAME = "DummyConnectionService";
     private static final String SECONDARY_USER_NAME = "NumberBlockingTest SecondaryUser";
+    private static final String FEATURE_TELEPHONY = "android.hardware.telephony";
+    private static final String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice";
 
     private int mSecondaryUserId;
     private int mPrimaryUserSerialNumber;
     private int mSecondaryUserSerialNumber;
 
     private IBuildInfo mCtsBuild;
+    private boolean mHasFeature;
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
 
-        if (!getDevice().isMultiUserSupported()) {
+        mHasFeature = getDevice().isMultiUserSupported()
+                && getDevice().hasFeature(FEATURE_TELEPHONY)
+                && getDevice().hasFeature(FEATURE_CONNECTION_SERVICE);
+
+        if (!mHasFeature) {
             return;
         }
 
@@ -70,7 +77,7 @@
 
     @Override
     protected void tearDown() throws Exception {
-        if (getDevice().isMultiUserSupported()) {
+        if (mHasFeature) {
             getDevice().removeUser(mSecondaryUserId);
         }
 
@@ -83,7 +90,9 @@
     }
 
     public void testNumberBlocking() throws Exception {
-        if (!getDevice().isMultiUserSupported()) {
+        if (!mHasFeature) {
+            LogUtil.CLog.logAndDisplay(Log.LogLevel.INFO,
+                    "Skipping number blocking test as the feature is not supported.");
             return;
         }
 
diff --git a/hostsidetests/sample/src/android/sample/cts/SampleHostResultTest.java b/hostsidetests/sample/src/android/sample/cts/SampleHostResultTest.java
index 808899a..5730ee0 100644
--- a/hostsidetests/sample/src/android/sample/cts/SampleHostResultTest.java
+++ b/hostsidetests/sample/src/android/sample/cts/SampleHostResultTest.java
@@ -44,6 +44,11 @@
     private static final String TAG = SampleHostResultTest.class.getSimpleName();
 
     /**
+     * Name of the report log to store test metrics.
+     */
+    private static final String REPORT_LOG_NAME = "SampleHostTestMetrics";
+
+    /**
      * The number of times to repeat the test.
      */
     private static final int REPEAT = 5;
@@ -132,15 +137,14 @@
         // Compute the stats.
         Stat.StatResult stat = Stat.getStat(result);
         // Get the report for this test and add the results to record.
-        String reportLogName = "SampleHostTestMetrics";
         String streamName = "test_transfer_time_metrics";
         MetricsReportLog report = new MetricsReportLog(mDevice.getSerialNumber(), mAbi.getName(),
                 String.format("%s#testTransferTime", getClass().getCanonicalName()),
-                reportLogName, streamName);
+                REPORT_LOG_NAME, streamName);
         report.addValues("times", result, ResultType.LOWER_BETTER, ResultUnit.MS);
         report.addValue("min", stat.mMin, ResultType.LOWER_BETTER, ResultUnit.MS);
         report.addValue("max", stat.mMax, ResultType.LOWER_BETTER, ResultUnit.MS);
-        // Every report must have a summary,
+        // Set a summary.
         report.setSummary("average", stat.mAverage, ResultType.LOWER_BETTER, ResultUnit.MS);
         // Send the report to Tradefed.
         report.submit();
diff --git a/tests/tests/draganddrop/Android.mk b/hostsidetests/services/windowmanager/Android.mk
similarity index 62%
rename from tests/tests/draganddrop/Android.mk
rename to hostsidetests/services/windowmanager/Android.mk
index 5042624..eeac27b 100644
--- a/tests/tests/draganddrop/Android.mk
+++ b/hostsidetests/services/windowmanager/Android.mk
@@ -1,4 +1,4 @@
-# Copyright (C) 2016 The Android Open Source Project
+# Copyright (C) 2015 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -12,30 +12,27 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-LOCAL_PATH:= $(call my-dir)
+LOCAL_PATH := $(call my-dir)
 
 include $(CLEAR_VARS)
 
-# Don't include this package in any target
 LOCAL_MODULE_TAGS := tests
-# When built, explicitly put it in the data partition.
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_DEX_PREOPT := false
-
-LOCAL_PROGUARD_ENABLED := disabled
+# Must match the package name in OldCtsTestCaseList.mk
+LOCAL_MODULE := CtsDragAndDropHostTestCases
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_STATIC_JAVA_LIBRARIES := ub-uiautomator android-support-test
+LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed-prebuilt
+
+LOCAL_CTS_TEST_PACKAGE := android.wm.cts
+
+LOCAL_CTS_MODULE_CONFIG := $(LOCAL_PATH)/Old$(CTS_MODULE_TEST_CONFIG)
 
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts
 
-LOCAL_PACKAGE_NAME := CtsDragAndDropTestCases
+include $(BUILD_CTS_HOST_JAVA_LIBRARY)
 
-LOCAL_SDK_VERSION := test_current
-
-include $(BUILD_PACKAGE)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
\ No newline at end of file
+# Build the test APKs using their own makefiles
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/draganddrop/AndroidTest.xml b/hostsidetests/services/windowmanager/AndroidTest.xml
similarity index 63%
rename from tests/tests/draganddrop/AndroidTest.xml
rename to hostsidetests/services/windowmanager/AndroidTest.xml
index ef2f491..5e305cf 100644
--- a/tests/tests/draganddrop/AndroidTest.xml
+++ b/hostsidetests/services/windowmanager/AndroidTest.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
+<!-- Copyright (C) 2015 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -13,15 +13,13 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-
-<configuration description="Config for the CTS Drag and Drop tests">
+<configuration description="Config for CTS drag and drop host test cases">
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.ApkInstaller">
         <option name="cleanup-apks" value="true" />
-        <option name="test-file-name" value="CtsDragSourceApp.apk" />
-        <option name="test-file-name" value="CtsDropTargetApp.apk" />
-        <option name="test-file-name" value="CtsDragAndDropTestCases.apk" />
+        <option name="test-file-name" value="CtsDragAndDropSourceApp.apk" />
+        <option name="test-file-name" value="CtsDragAndDropTargetApp.apk" />
     </target_preparer>
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
-        <option name="package" value="android.dnd.cts" />
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsDragAndDropHostTestCases.jar" />
     </test>
-</configuration>
\ No newline at end of file
+</configuration>
diff --git a/tests/tests/draganddrop/droptarget/res/values/strings.xml b/hostsidetests/services/windowmanager/OldAndroidTest.xml
similarity index 61%
rename from tests/tests/draganddrop/droptarget/res/values/strings.xml
rename to hostsidetests/services/windowmanager/OldAndroidTest.xml
index ea0cc34..844bca3 100644
--- a/tests/tests/draganddrop/droptarget/res/values/strings.xml
+++ b/hostsidetests/services/windowmanager/OldAndroidTest.xml
@@ -13,7 +13,9 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-
-<resources>
-     <string name="not_available_label">N/A</string>
-</resources>
+<configuration description="CTS package preparer for install/uninstall of the apk used as a test operation target">
+    <include name="common-config" />
+    <!-- This will tell tradefed to install the test apk. -->
+    <option name="cts-apk-installer:test-file-name" value="CtsDragAndDropSourceApp.apk" />
+    <option name="cts-apk-installer:test-file-name" value="CtsDragAndDropTargetApp.apk" />
+</configuration>
diff --git a/tests/tests/draganddrop/dragsource/Android.mk b/hostsidetests/services/windowmanager/dndsourceapp/Android.mk
similarity index 75%
rename from tests/tests/draganddrop/dragsource/Android.mk
rename to hostsidetests/services/windowmanager/dndsourceapp/Android.mk
index 066759d..1ec751c 100644
--- a/tests/tests/draganddrop/dragsource/Android.mk
+++ b/hostsidetests/services/windowmanager/dndsourceapp/Android.mk
@@ -16,22 +16,16 @@
 
 include $(CLEAR_VARS)
 
-# Don't include this package in any target
+# Don't include this package in any target.
 LOCAL_MODULE_TAGS := tests
-# When built, explicitly put it in the data partition.
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-
-LOCAL_DEX_PREOPT := false
-
-LOCAL_PROGUARD_ENABLED := disabled
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
+LOCAL_SDK_VERSION := current
+
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts
 
-LOCAL_PACKAGE_NAME := CtsDragSourceApp
+LOCAL_PACKAGE_NAME := CtsDragAndDropSourceApp
 
-LOCAL_SDK_VERSION := current
-
-include $(BUILD_PACKAGE)
\ No newline at end of file
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/tests/draganddrop/dragsource/AndroidManifest.xml b/hostsidetests/services/windowmanager/dndsourceapp/AndroidManifest.xml
similarity index 75%
rename from tests/tests/draganddrop/dragsource/AndroidManifest.xml
rename to hostsidetests/services/windowmanager/dndsourceapp/AndroidManifest.xml
index d7c9011..296a979 100644
--- a/tests/tests/draganddrop/dragsource/AndroidManifest.xml
+++ b/hostsidetests/services/windowmanager/dndsourceapp/AndroidManifest.xml
@@ -15,16 +15,17 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.dnd.cts.dragsource">
-    <application android:label="DragSource">
-        <activity android:name="android.dnd.cts.dragsource.DragSource">
+        package="android.wm.cts.dndsourceapp">
+    <application android:label="CtsDnDSource">
+        <activity android:name="android.wm.cts.dndsourceapp.DragSource">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <provider android:name=".DragSourceContentProvider"
-                  android:authorities="android.dnd.cts.dragsource.contentprovider"
+
+        <provider android:name="android.wm.cts.dndsourceapp.DragSourceContentProvider"
+                  android:authorities="android.wm.cts.dndsource.contentprovider"
                   android:grantUriPermissions="true"/>
     </application>
 </manifest>
diff --git a/tests/tests/draganddrop/dragsource/res/layout/main_activity.xml b/hostsidetests/services/windowmanager/dndsourceapp/res/layout/source_activity.xml
similarity index 85%
rename from tests/tests/draganddrop/dragsource/res/layout/main_activity.xml
rename to hostsidetests/services/windowmanager/dndsourceapp/res/layout/source_activity.xml
index 98c8202..673994c 100644
--- a/tests/tests/draganddrop/dragsource/res/layout/main_activity.xml
+++ b/hostsidetests/services/windowmanager/dndsourceapp/res/layout/source_activity.xml
@@ -19,13 +19,10 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:orientation="vertical">
-
     <TextView
-            android:id="@+id/source"
+            android:id="@+id/drag_source"
             android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:gravity="center"
-            android:background="#ddd">
+            android:layout_height="match_parent"
+            android:gravity="center">
     </TextView>
-
 </LinearLayout>
\ No newline at end of file
diff --git a/tests/tests/draganddrop/dragsource/src/android/dnd/cts/dragsource/DragSource.java b/hostsidetests/services/windowmanager/dndsourceapp/src/android/wm/cts/dndsourceapp/DragSource.java
similarity index 85%
rename from tests/tests/draganddrop/dragsource/src/android/dnd/cts/dragsource/DragSource.java
rename to hostsidetests/services/windowmanager/dndsourceapp/src/android/wm/cts/dndsourceapp/DragSource.java
index df93852..ff92656 100644
--- a/tests/tests/draganddrop/dragsource/src/android/dnd/cts/dragsource/DragSource.java
+++ b/hostsidetests/services/windowmanager/dndsourceapp/src/android/wm/cts/dndsourceapp/DragSource.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.dnd.cts.dragsource;
+package android.wm.cts.dndsourceapp;
 
 import android.app.Activity;
 import android.content.ClipData;
@@ -23,6 +23,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.PersistableBundle;
+import android.view.MotionEvent;
 import android.view.View;
 import android.widget.TextView;
 
@@ -31,13 +32,13 @@
             "content://" + DragSourceContentProvider.AUTHORITY + "/data";
 
     private static final String MAGIC_VALUE = "42";
-    public static final long TIMEOUT_CANCEL = 150;
+    private static final long TIMEOUT_CANCEL = 150;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        View view = getLayoutInflater().inflate(R.layout.main_activity, null);
+        View view = getLayoutInflater().inflate(R.layout.source_activity, null);
         setContentView(view);
 
         final Uri plainUri = Uri.parse(URI_PREFIX + "/" + MAGIC_VALUE);
@@ -45,7 +46,7 @@
         setUpDragSource("disallow_global", plainUri, 0);
         setUpDragSource("cancel_soon", plainUri, View.DRAG_FLAG_GLOBAL);
 
-        setUpDragSource("dont_grant", plainUri, View.DRAG_FLAG_GLOBAL);
+        setUpDragSource("grant_none", plainUri, View.DRAG_FLAG_GLOBAL);
         setUpDragSource("grant_read", plainUri,
                 View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ);
         setUpDragSource("grant_write", plainUri,
@@ -61,18 +62,20 @@
                         View.DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION);
         setUpDragSource("grant_read_noprefix", prefixUri,
                 View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ);
-
     }
 
     private void setUpDragSource(String mode, final Uri uri, final int flags) {
         if (!mode.equals(getIntent().getStringExtra("mode"))) {
             return;
         }
-        final View source = findViewById(R.id.source);
+        final View source = findViewById(R.id.drag_source);
         ((TextView) source).setText(mode);
-        source.setOnLongClickListener(new View.OnLongClickListener() {
+        source.setOnTouchListener(new View.OnTouchListener() {
             @Override
-            public boolean onLongClick(final View v) {
+            public boolean onTouch(View v, MotionEvent event) {
+                if (event.getAction() != MotionEvent.ACTION_DOWN) {
+                    return false;
+                }
                 final ClipDescription clipDescription = new ClipDescription("", new String[] {
                         ClipDescription.MIMETYPE_TEXT_URILIST });
                 PersistableBundle extras = new PersistableBundle(1);
@@ -92,7 +95,7 @@
                         }
                     }, TIMEOUT_CANCEL);
                 }
-                return false;
+                return true;
             }
         });
     }
diff --git a/tests/tests/draganddrop/dragsource/src/android/dnd/cts/dragsource/DragSourceContentProvider.java b/hostsidetests/services/windowmanager/dndsourceapp/src/android/wm/cts/dndsourceapp/DragSourceContentProvider.java
similarity index 93%
rename from tests/tests/draganddrop/dragsource/src/android/dnd/cts/dragsource/DragSourceContentProvider.java
rename to hostsidetests/services/windowmanager/dndsourceapp/src/android/wm/cts/dndsourceapp/DragSourceContentProvider.java
index 5b93832..06a94aa 100644
--- a/tests/tests/draganddrop/dragsource/src/android/dnd/cts/dragsource/DragSourceContentProvider.java
+++ b/hostsidetests/services/windowmanager/dndsourceapp/src/android/wm/cts/dndsourceapp/DragSourceContentProvider.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.dnd.cts.dragsource;
+package android.wm.cts.dndsourceapp;
 
 import android.content.ContentProvider;
 import android.content.ContentValues;
@@ -24,7 +24,7 @@
 
 public class DragSourceContentProvider extends ContentProvider {
 
-    public static final String AUTHORITY = "android.dnd.cts.dragsource.contentprovider";
+    public static final String AUTHORITY = "android.wm.cts.dndsource.contentprovider";
 
     private static final UriMatcher sMatcher = new UriMatcher(UriMatcher.NO_MATCH);
 
diff --git a/tests/tests/draganddrop/dragsource/src/android/dnd/cts/dragsource/DragSourceCursor.java b/hostsidetests/services/windowmanager/dndsourceapp/src/android/wm/cts/dndsourceapp/DragSourceCursor.java
similarity index 97%
rename from tests/tests/draganddrop/dragsource/src/android/dnd/cts/dragsource/DragSourceCursor.java
rename to hostsidetests/services/windowmanager/dndsourceapp/src/android/wm/cts/dndsourceapp/DragSourceCursor.java
index aa036fb..c54df65 100644
--- a/tests/tests/draganddrop/dragsource/src/android/dnd/cts/dragsource/DragSourceCursor.java
+++ b/hostsidetests/services/windowmanager/dndsourceapp/src/android/wm/cts/dndsourceapp/DragSourceCursor.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.dnd.cts.dragsource;
+package android.wm.cts.dndsourceapp;
 
 import android.database.AbstractCursor;
 
diff --git a/tests/tests/draganddrop/dragsource/Android.mk b/hostsidetests/services/windowmanager/dndtargetapp/Android.mk
similarity index 75%
copy from tests/tests/draganddrop/dragsource/Android.mk
copy to hostsidetests/services/windowmanager/dndtargetapp/Android.mk
index 066759d..7dc512c 100644
--- a/tests/tests/draganddrop/dragsource/Android.mk
+++ b/hostsidetests/services/windowmanager/dndtargetapp/Android.mk
@@ -16,22 +16,16 @@
 
 include $(CLEAR_VARS)
 
-# Don't include this package in any target
+# Don't include this package in any target.
 LOCAL_MODULE_TAGS := tests
-# When built, explicitly put it in the data partition.
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-
-LOCAL_DEX_PREOPT := false
-
-LOCAL_PROGUARD_ENABLED := disabled
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
+LOCAL_SDK_VERSION := current
+
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts
 
-LOCAL_PACKAGE_NAME := CtsDragSourceApp
+LOCAL_PACKAGE_NAME := CtsDragAndDropTargetApp
 
-LOCAL_SDK_VERSION := current
-
-include $(BUILD_PACKAGE)
\ No newline at end of file
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/tests/draganddrop/droptarget/AndroidManifest.xml b/hostsidetests/services/windowmanager/dndtargetapp/AndroidManifest.xml
similarity index 85%
rename from tests/tests/draganddrop/droptarget/AndroidManifest.xml
rename to hostsidetests/services/windowmanager/dndtargetapp/AndroidManifest.xml
index 83d7005..ed7b9c2 100644
--- a/tests/tests/draganddrop/droptarget/AndroidManifest.xml
+++ b/hostsidetests/services/windowmanager/dndtargetapp/AndroidManifest.xml
@@ -15,9 +15,9 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.dnd.cts.droptarget">
-    <application android:label="DropTarget">
-        <activity android:name="android.dnd.cts.droptarget.DropTarget">
+        package="android.wm.cts.dndtargetapp">
+    <application android:label="CtsDnDTarget">
+        <activity android:name="android.wm.cts.dndtargetapp.DropTarget">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
diff --git a/tests/tests/draganddrop/dragsource/res/layout/main_activity.xml b/hostsidetests/services/windowmanager/dndtargetapp/res/layout/target_activity.xml
similarity index 85%
copy from tests/tests/draganddrop/dragsource/res/layout/main_activity.xml
copy to hostsidetests/services/windowmanager/dndtargetapp/res/layout/target_activity.xml
index 98c8202..ddcdf33 100644
--- a/tests/tests/draganddrop/dragsource/res/layout/main_activity.xml
+++ b/hostsidetests/services/windowmanager/dndtargetapp/res/layout/target_activity.xml
@@ -19,13 +19,10 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:orientation="vertical">
-
     <TextView
-            android:id="@+id/source"
+            android:id="@+id/drag_target"
             android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:gravity="center"
-            android:background="#ddd">
+            android:layout_height="match_parent"
+            android:gravity="center">
     </TextView>
-
 </LinearLayout>
\ No newline at end of file
diff --git a/tests/tests/draganddrop/droptarget/src/android/dnd/cts/droptarget/DropTarget.java b/hostsidetests/services/windowmanager/dndtargetapp/src/android/wm/cts/dndtargetapp/DropTarget.java
similarity index 81%
rename from tests/tests/draganddrop/droptarget/src/android/dnd/cts/droptarget/DropTarget.java
rename to hostsidetests/services/windowmanager/dndtargetapp/src/android/wm/cts/dndtargetapp/DropTarget.java
index 62c7d16..904a422 100644
--- a/tests/tests/draganddrop/droptarget/src/android/dnd/cts/droptarget/DropTarget.java
+++ b/hostsidetests/services/windowmanager/dndtargetapp/src/android/wm/cts/dndtargetapp/DropTarget.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.dnd.cts.droptarget;
+package android.wm.cts.dndtargetapp;
 
 import android.app.Activity;
 import android.content.ClipData;
@@ -23,24 +23,35 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.PersistableBundle;
+import android.util.Log;
 import android.view.DragAndDropPermissions;
 import android.view.DragEvent;
 import android.view.View;
 import android.widget.TextView;
 
 public class DropTarget extends Activity {
+    public static final String LOG_TAG = "DropTarget";
 
-    private static final String MAGIC_VALUE = "42";
+    private static final String RESULT_KEY_DRAG_STARTED = "DRAG_STARTED";
+    private static final String RESULT_KEY_EXTRAS = "EXTRAS";
+    private static final String RESULT_KEY_DROP_RESULT = "DROP";
+    private static final String RESULT_KEY_DETAILS = "DETAILS";
+
     public static final String RESULT_OK = "OK";
+    public static final String RESULT_EXCEPTION = "Exception";
+
+    protected static final String MAGIC_VALUE = "42";
+
+    private TextView mTextView;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        View view = getLayoutInflater().inflate(R.layout.main_activity, null);
+        View view = getLayoutInflater().inflate(R.layout.target_activity, null);
         setContentView(view);
 
-        setUpDropTarget("dont_request", new OnDragUriReadListener(false));
+        setUpDropTarget("request_none", new OnDragUriReadListener(false));
         setUpDropTarget("request_read", new OnDragUriReadListener());
         setUpDropTarget("request_write", new OnDragUriWriteListener());
         setUpDropTarget("request_read_nested", new OnDragUriReadPrefixListener());
@@ -51,9 +62,9 @@
         if (!mode.equals(getIntent().getStringExtra("mode"))) {
             return;
         }
-        final View target = findViewById(R.id.target);
-        ((TextView) target).setText(mode);
-        target.setOnDragListener(listener);
+        mTextView = (TextView)findViewById(R.id.drag_target);
+        mTextView.setText(mode);
+        mTextView.setOnDragListener(listener);
     }
 
     private String checkExtraValue(DragEvent event) {
@@ -69,6 +80,11 @@
         return value;
     }
 
+    private void logResult(String key, String value) {
+        Log.i(LOG_TAG, key + "=" + value);
+        mTextView.setText(mTextView.getText() + "\n" + key + "=" + value);
+    }
+
     private abstract class OnDragUriListener implements View.OnDragListener {
         private final boolean requestPermissions;
 
@@ -80,8 +96,8 @@
         public boolean onDrag(View v, DragEvent event) {
             switch (event.getAction()) {
                 case DragEvent.ACTION_DRAG_STARTED:
-                    ((TextView) findViewById(R.id.drag_started)).setText("DRAG_STARTED");
-                    ((TextView) findViewById(R.id.extra_value)).setText(checkExtraValue(event));
+                    logResult(RESULT_KEY_DRAG_STARTED, RESULT_OK);
+                    logResult(RESULT_KEY_EXTRAS, checkExtraValue(event));
                     return true;
 
                 case DragEvent.ACTION_DRAG_ENTERED:
@@ -98,10 +114,10 @@
                     try {
                         result = processDrop(event, requestPermissions);
                     } catch (Exception e) {
-                        result = "Exception";
-                        ((TextView) findViewById(R.id.details)).setText(e.getMessage());
+                        result = RESULT_EXCEPTION;
+                        logResult(RESULT_KEY_DETAILS, e.getMessage());
                     }
-                    ((TextView) findViewById(R.id.result)).setText(result);
+                    logResult(RESULT_KEY_DROP_RESULT, result);
                     return true;
 
                 case DragEvent.ACTION_DRAG_ENDED:
@@ -150,11 +166,11 @@
     }
 
     private class OnDragUriReadListener extends OnDragUriListener {
-        public OnDragUriReadListener(boolean requestPermissions) {
+        OnDragUriReadListener(boolean requestPermissions) {
             super(requestPermissions);
         }
 
-        public OnDragUriReadListener() {
+        OnDragUriReadListener() {
             super(true);
         }
 
@@ -184,7 +200,7 @@
     }
 
     private class OnDragUriWriteListener extends OnDragUriListener {
-        public OnDragUriWriteListener() {
+        OnDragUriWriteListener() {
             super(true);
         }
 
@@ -217,7 +233,7 @@
     }
 
     private class OnDragUriTakePersistableListener extends OnDragUriListener {
-        public OnDragUriTakePersistableListener() {
+        OnDragUriTakePersistableListener() {
             super(true);
         }
 
diff --git a/hostsidetests/services/windowmanager/src/android/wm/cts/CrossAppDragAndDropTests.java b/hostsidetests/services/windowmanager/src/android/wm/cts/CrossAppDragAndDropTests.java
new file mode 100644
index 0000000..5dc789a
--- /dev/null
+++ b/hostsidetests/services/windowmanager/src/android/wm/cts/CrossAppDragAndDropTests.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright (C) 2016 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.wm.cts;
+
+import com.android.tradefed.device.CollectingOutputReceiver;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceTestCase;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class CrossAppDragAndDropTests extends DeviceTestCase {
+    // Constants copied from ActivityManager.StackId. If they are changed there, these must be
+    // updated.
+    /** ID of stack where fullscreen activities are normally launched into. */
+    private static final int FULLSCREEN_WORKSPACE_STACK_ID = 1;
+
+    /** ID of stack where freeform/resized activities are normally launched into. */
+    private static final int FREEFORM_WORKSPACE_STACK_ID = FULLSCREEN_WORKSPACE_STACK_ID + 1;
+
+    /** ID of stack that occupies a dedicated region of the screen. */
+    private static final int DOCKED_STACK_ID = FREEFORM_WORKSPACE_STACK_ID + 1;
+
+    private static final String AM_FORCE_STOP = "am force-stop ";
+    private static final String AM_MOVE_TASK = "am stack movetask ";
+    private static final String AM_START_N = "am start -n ";
+    private static final String AM_STACK_LIST = "am stack list";
+    private static final String INPUT_MOUSE_SWIPE = "input mouse swipe ";
+
+    private static final String TASK_ID_PREFIX = "taskId";
+
+    private static final int SWIPE_DURATION_MS = 500;
+
+    private static final String SOURCE_PACKAGE_NAME = "android.wm.cts.dndsourceapp";
+    private static final String TARGET_PACKAGE_NAME = "android.wm.cts.dndtargetapp";
+
+    private static final String SOURCE_ACTIVITY_NAME = "DragSource";
+    private static final String TARGET_ACTIVITY_NAME = "DropTarget";
+
+    private static final String DISALLOW_GLOBAL = "disallow_global";
+    private static final String CANCEL_SOON = "cancel_soon";
+    private static final String GRANT_NONE = "grant_none";
+    private static final String GRANT_READ = "grant_read";
+    private static final String GRANT_WRITE = "grant_write";
+    private static final String GRANT_READ_PREFIX = "grant_read_prefix";
+    private static final String GRANT_READ_NOPREFIX = "grant_read_noprefix";
+    private static final String GRANT_READ_PERSISTABLE = "grant_read_persistable";
+
+    private static final String REQUEST_NONE = "request_none";
+    private static final String REQUEST_READ = "request_read";
+    private static final String REQUEST_READ_NESTED = "request_read_nested";
+    private static final String REQUEST_TAKE_PERSISTABLE = "request_take_persistable";
+    private static final String REQUEST_WRITE = "request_write";
+
+    private static final String TARGET_LOG_TAG = "DropTarget";
+
+    private static final String RESULT_KEY_DRAG_STARTED = "DRAG_STARTED";
+    private static final String RESULT_KEY_EXTRAS = "EXTRAS";
+    private static final String RESULT_KEY_DROP_RESULT = "DROP";
+
+    private static final String RESULT_OK = "OK";
+    private static final String RESULT_EXCEPTION = "Exception";
+    private static final String RESULT_NULL_DROP_PERMISSIONS = "Null DragAndDropPermissions";
+
+    private ITestDevice mDevice;
+
+    private Map<String, String> mResults;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mDevice = getDevice();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        mDevice.executeShellCommand(AM_FORCE_STOP + SOURCE_PACKAGE_NAME);
+        mDevice.executeShellCommand(AM_FORCE_STOP + TARGET_PACKAGE_NAME);
+    }
+
+    private String adbShell(String command) throws DeviceNotAvailableException {
+        return mDevice.executeShellCommand(command);
+    }
+
+    private void clearLogs() throws DeviceNotAvailableException {
+        adbShell("logcat -c");
+    }
+
+    private String getStartCommand(String componentName, String modeExtra) {
+        return AM_START_N + componentName + " -e mode " + modeExtra;
+    }
+
+    private String getMoveTaskCommand(int taskId, int stackId) throws Exception {
+        return AM_MOVE_TASK + taskId + " " + stackId + " true";
+    }
+
+    private String getComponentName(String packageName, String activityName) {
+        return packageName + "/" + packageName + "." + activityName;
+    }
+
+    private void launchDockedActivity(String packageName, String activityName, String mode)
+            throws Exception {
+        clearLogs();
+        final String componentName = getComponentName(packageName, activityName);
+        adbShell(getStartCommand(componentName, mode));
+        final int taskId = getActivityTaskId(componentName);
+        adbShell(getMoveTaskCommand(taskId, DOCKED_STACK_ID));
+        waitForResume(packageName, activityName);
+    }
+
+    private void launchFreeformActivity(String packageName, String activityName, String mode)
+            throws Exception {
+        clearLogs();
+        final String componentName = getComponentName(packageName, activityName);
+        adbShell(getStartCommand(componentName, mode) + " --stack " + FREEFORM_WORKSPACE_STACK_ID);
+        waitForResume(packageName, activityName);
+    }
+
+    private void waitForResume(String packageName, String activityName) throws Exception {
+        final String fullActivityName = packageName + "." + activityName;
+        int retryCount = 3;
+        do {
+            String logs = adbShell("logcat -d -b events");
+            for (String line : logs.split("\\n")) {
+                if(line.contains("am_on_resume_called") && line.contains(fullActivityName)) {
+                    return;
+                }
+            }
+        } while (retryCount-- > 0);
+
+        throw new Exception(fullActivityName + " has failed to start");
+    }
+
+    private void injectInput(Point from, Point to, int durationMs) throws Exception {
+        adbShell(INPUT_MOUSE_SWIPE + from.x + " " + from.y + " " + to.x + " " + to.y + " " +
+                durationMs);
+    }
+
+    static class Point {
+        public int x, y;
+
+        public Point(int _x, int _y) {
+            x=_x;
+            y=_y;
+        }
+
+        public Point() {}
+    }
+
+    private String findTaskInfo(String name) throws Exception {
+        CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
+        mDevice.executeShellCommand(AM_STACK_LIST, outputReceiver);
+        final String output = outputReceiver.getOutput();
+        for (String line : output.split("\\n")) {
+            if (line.contains(name)) {
+                return line;
+            }
+        }
+        return "";
+    }
+
+    private boolean getWindowBounds(String name, Point from, Point to) throws Exception {
+        final String taskInfo = findTaskInfo(name);
+        final String[] sections = taskInfo.split("\\[");
+        if (sections.length > 2) {
+            try {
+                parsePoint(sections[1], from);
+                parsePoint(sections[2], to);
+                return true;
+            } catch (Exception e) {
+                return false;
+            }
+        }
+        return false;
+    }
+
+    private int getActivityTaskId(String name) throws Exception {
+        final String taskInfo = findTaskInfo(name);
+        for (String word : taskInfo.split("\\s+")) {
+            if (word.startsWith(TASK_ID_PREFIX)) {
+                final String withColon = word.split("=")[1];
+                return Integer.parseInt(withColon.substring(0, withColon.length() - 1));
+            }
+        }
+        return -1;
+    }
+
+    private Point getWindowCenter(String name) throws Exception {
+        Point p1 = new Point();
+        Point p2 = new Point();
+        if (getWindowBounds(name, p1, p2)) {
+            return new Point((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
+        }
+        return null;
+    }
+
+    private void parsePoint(String string, Point point) {
+        final String[] parts = string.split("[,|\\]]");
+        point.x = Integer.parseInt(parts[0]);
+        point.y = Integer.parseInt(parts[1]);
+    }
+
+    private Map<String, String> getLogResults(String className) throws Exception {
+        int retryCount = 3;
+        Map<String, String> output = new HashMap<String, String>();
+        do {
+
+            String logs = adbShell("logcat -v brief -d " + className + ":I" + " *:S");
+            for (String line : logs.split("\\n")) {
+                if (line.startsWith("I/" + className)) {
+                    String payload = line.split(":")[1].trim();
+                    final String[] split = payload.split("=");
+                    if (split.length > 1) {
+                        output.put(split[0], split[1]);
+                    }
+                }
+            }
+            if (output.containsKey(RESULT_KEY_DROP_RESULT)) {
+                return output;
+            }
+        } while (retryCount-- > 0);
+        return output;
+    }
+
+    private void doTestDragAndDrop(String sourceMode, String targetMode, String expectedDropResult)
+            throws Exception {
+
+        launchDockedActivity(SOURCE_PACKAGE_NAME, SOURCE_ACTIVITY_NAME, sourceMode);
+        launchFreeformActivity(TARGET_PACKAGE_NAME, TARGET_ACTIVITY_NAME, targetMode);
+
+        clearLogs();
+
+        injectInput(
+                getWindowCenter(getComponentName(SOURCE_PACKAGE_NAME, SOURCE_ACTIVITY_NAME)),
+                getWindowCenter(getComponentName(TARGET_PACKAGE_NAME, TARGET_ACTIVITY_NAME)),
+                SWIPE_DURATION_MS);
+
+        mResults = getLogResults(TARGET_LOG_TAG);
+        assertResult(RESULT_KEY_DROP_RESULT, expectedDropResult);
+    }
+
+
+    private void assertResult(String resultKey, String expectedResult) {
+        if (expectedResult == null) {
+            if (mResults.containsKey(resultKey)) {
+                fail("Unexpected " + resultKey + "=" + mResults.get(resultKey));
+            }
+        } else {
+            assertTrue("Missing " + resultKey, mResults.containsKey(resultKey));
+            assertEquals(expectedResult, mResults.get(resultKey));
+        }
+    }
+
+    public void testCancelSoon() throws Exception {
+        doTestDragAndDrop(CANCEL_SOON, REQUEST_NONE, null);
+        assertResult(RESULT_KEY_DRAG_STARTED, RESULT_OK);
+        assertResult(RESULT_KEY_EXTRAS, RESULT_OK);
+    }
+
+    public void testDisallowGlobal() throws Exception {
+        doTestDragAndDrop(DISALLOW_GLOBAL, REQUEST_NONE, null);
+        assertResult(RESULT_KEY_DRAG_STARTED, null);
+    }
+
+    public void testGrantNoneRequestNone() throws Exception {
+        doTestDragAndDrop(GRANT_NONE, REQUEST_NONE, RESULT_EXCEPTION);
+        assertResult(RESULT_KEY_DRAG_STARTED, RESULT_OK);
+        assertResult(RESULT_KEY_EXTRAS, RESULT_OK);
+    }
+
+    public void testGrantNoneRequestRead() throws Exception {
+        doTestDragAndDrop(GRANT_NONE, REQUEST_READ, RESULT_NULL_DROP_PERMISSIONS);
+    }
+
+    public void testGrantNoneRequestWrite() throws Exception {
+        doTestDragAndDrop(GRANT_NONE, REQUEST_WRITE, RESULT_NULL_DROP_PERMISSIONS);
+    }
+
+    public void testGrantReadRequestNone() throws Exception {
+        doTestDragAndDrop(GRANT_READ, REQUEST_NONE, RESULT_EXCEPTION);
+    }
+
+    public void testGrantReadRequestRead() throws Exception {
+        doTestDragAndDrop(GRANT_READ, REQUEST_READ, RESULT_OK);
+    }
+
+    public void testGrantReadRequestWrite() throws Exception {
+        doTestDragAndDrop(GRANT_READ, REQUEST_WRITE, RESULT_EXCEPTION);
+    }
+
+    public void testGrantReadNoPrefixRequestReadNested() throws Exception {
+        doTestDragAndDrop(GRANT_READ_NOPREFIX, REQUEST_READ_NESTED, RESULT_EXCEPTION);
+    }
+
+    public void testGrantReadPrefixRequestReadNested() throws Exception {
+        doTestDragAndDrop(GRANT_READ_PREFIX, REQUEST_READ_NESTED, RESULT_OK);
+    }
+
+    public void testGrantPersistableRequestTakePersistable() throws Exception {
+        doTestDragAndDrop(GRANT_READ_PERSISTABLE, REQUEST_TAKE_PERSISTABLE, RESULT_OK);
+    }
+
+    public void testGrantReadRequestTakePersistable() throws Exception {
+        doTestDragAndDrop(GRANT_READ, REQUEST_TAKE_PERSISTABLE, RESULT_EXCEPTION);
+    }
+
+    public void testGrantWriteRequestNone() throws Exception {
+        doTestDragAndDrop(GRANT_WRITE, REQUEST_NONE, RESULT_EXCEPTION);
+    }
+
+    public void testGrantWriteRequestRead() throws Exception {
+        doTestDragAndDrop(GRANT_WRITE, REQUEST_READ, RESULT_EXCEPTION);
+    }
+
+    public void testGrantWriteRequestWrite() throws Exception {
+        doTestDragAndDrop(GRANT_WRITE, REQUEST_WRITE, RESULT_OK);
+    }
+}
diff --git a/hostsidetests/theme/README b/hostsidetests/theme/README
index bce711a..d585db3 100644
--- a/hostsidetests/theme/README
+++ b/hostsidetests/theme/README
@@ -42,11 +42,15 @@
      reference images are saved in assets/<api>/<dpi>.zip and will overwrite
      any existing sets. Image generation may be started using:
 
-     .cts/hostsidetests/theme/generate_images.sh
+     ./cts/hostsidetests/theme/generate_images.sh
 
 A complete collection of reference images for a given API revision must include
 a set for each possible DPI bucket (tvdpi, xxhdpi, etc.) that may be tested.
 
+For a list of devices and their DPI buckets, see Device Metrics:
+
+    https://design.google.com/devices/
+
 
 II. Building theme tests
 
@@ -68,6 +72,8 @@
 
 2. Run the theme tests using cts-tradefed:
 
-   cts-tradefed run cts -c android.theme.cts.ThemeHostTest
+   cts-tradefed run singleCommand cts --skip-device-info --skip-preconditions \
+       --skip-connectivity-check --module CtsThemeHostTestCases \
+       --test android.theme.cts.ThemeHostTest
 
 3. Wait for the tests to complete. This should take less than five minutes.
diff --git a/hostsidetests/theme/android_device.py b/hostsidetests/theme/android_device.py
index 97b5fdd..6687494 100644
--- a/hostsidetests/theme/android_device.py
+++ b/hostsidetests/theme/android_device.py
@@ -31,7 +31,7 @@
     def runAdbCommand(self, cmd):
         self.waitForAdbDevice()
         adbCmd = "adb -s %s %s" %(self._adbDevice, cmd)
-        adbProcess = subprocess.Popen(adbCmd.split(" "), bufsize = -1, stdout = subprocess.PIPE)
+        adbProcess = subprocess.Popen(adbCmd.split(" "), bufsize = -1, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
         return adbProcess.communicate()
 
     def runShellCommand(self, cmd):
@@ -58,12 +58,12 @@
 
     def installApk(self, apkPath):
         (out, err) = self.runAdbCommand("install -r -d -g " + apkPath)
-        result = out.split()
+        result = err.split()
         return (out, err, "Success" in result)
 
     def uninstallApk(self, package):
         (out, err) = self.runAdbCommand("uninstall " + package)
-        result = out.split()
+        result = err.split()
         return "Success" in result
 
     def runInstrumentationTest(self, option):
diff --git a/hostsidetests/theme/app/AndroidManifest.xml b/hostsidetests/theme/app/AndroidManifest.xml
index 4e81ab0..2a1b59b 100755
--- a/hostsidetests/theme/app/AndroidManifest.xml
+++ b/hostsidetests/theme/app/AndroidManifest.xml
@@ -31,13 +31,6 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
-        <activity android:name=".DisplayInfoActivity"
-                  android:label="@string/display_info">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-        </activity>
         <activity android:name=".GenerateImagesActivity"
                   android:exported="true" />
     </application>
diff --git a/hostsidetests/theme/app/res/drawable-400dpi/display_info.png b/hostsidetests/theme/app/res/drawable-400dpi/display_info.png
deleted file mode 100644
index e5f1f96..0000000
--- a/hostsidetests/theme/app/res/drawable-400dpi/display_info.png
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/app/res/drawable-560dpi/display_info.png b/hostsidetests/theme/app/res/drawable-560dpi/display_info.png
deleted file mode 100644
index babe0da..0000000
--- a/hostsidetests/theme/app/res/drawable-560dpi/display_info.png
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/app/res/drawable-hdpi/display_info.png b/hostsidetests/theme/app/res/drawable-hdpi/display_info.png
deleted file mode 100644
index 10b3950..0000000
--- a/hostsidetests/theme/app/res/drawable-hdpi/display_info.png
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/app/res/drawable-ldpi/display_info.png b/hostsidetests/theme/app/res/drawable-ldpi/display_info.png
deleted file mode 100644
index af1fda5..0000000
--- a/hostsidetests/theme/app/res/drawable-ldpi/display_info.png
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/app/res/drawable-mdpi/display_info.png b/hostsidetests/theme/app/res/drawable-mdpi/display_info.png
deleted file mode 100644
index 4378b14..0000000
--- a/hostsidetests/theme/app/res/drawable-mdpi/display_info.png
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/app/res/drawable-tvdpi/display_info.png b/hostsidetests/theme/app/res/drawable-tvdpi/display_info.png
deleted file mode 100644
index d9825fb..0000000
--- a/hostsidetests/theme/app/res/drawable-tvdpi/display_info.png
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/app/res/drawable-xhdpi/display_info.png b/hostsidetests/theme/app/res/drawable-xhdpi/display_info.png
deleted file mode 100644
index 585af2f..0000000
--- a/hostsidetests/theme/app/res/drawable-xhdpi/display_info.png
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/app/res/drawable-xxhdpi/display_info.png b/hostsidetests/theme/app/res/drawable-xxhdpi/display_info.png
deleted file mode 100644
index 255c28f..0000000
--- a/hostsidetests/theme/app/res/drawable-xxhdpi/display_info.png
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/app/res/drawable-xxxhdpi/display_info.png b/hostsidetests/theme/app/res/drawable-xxxhdpi/display_info.png
deleted file mode 100644
index 095382c..0000000
--- a/hostsidetests/theme/app/res/drawable-xxxhdpi/display_info.png
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/app/res/layout/display_info.xml b/hostsidetests/theme/app/res/layout/display_info.xml
deleted file mode 100644
index 167d935..0000000
--- a/hostsidetests/theme/app/res/layout/display_info.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 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.
--->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="vertical"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    >
-    <ImageView
-        android:src="@drawable/display_info"
-        android:layout_width="fill_parent"
-        android:layout_height="wrap_content"
-        />
-    <TextView
-        android:id="@+id/text"
-        android:layout_width="fill_parent"
-        android:layout_height="wrap_content"
-        />
-</LinearLayout>
-
diff --git a/hostsidetests/theme/app/src/android/theme/app/DisplayInfoActivity.java b/hostsidetests/theme/app/src/android/theme/app/DisplayInfoActivity.java
deleted file mode 100644
index 530675d..0000000
--- a/hostsidetests/theme/app/src/android/theme/app/DisplayInfoActivity.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2014 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.theme.app;
-
-import android.app.Activity;
-import android.theme.app.R;
-import android.os.Bundle;
-import android.util.DisplayMetrics;
-import android.view.Display;
-import android.view.WindowManager;
-import android.widget.TextView;
-
-/**
- * An activity to display information about the device, including density
- * bucket and dimensions.
- */
-public class DisplayInfoActivity extends Activity {
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.display_info);
-
-        WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
-        Display display = windowManager.getDefaultDisplay();
-        DisplayMetrics metrics = new DisplayMetrics();
-        display.getMetrics(metrics);
-
-        DisplayMetrics dm = getResources().getDisplayMetrics();
-        int width = Math.round(dm.widthPixels / dm.density);
-        int height = Math.round(dm.heightPixels / dm.density);
-
-        TextView text = (TextView) findViewById(R.id.text);
-        text.setText(getString(R.string.display_info_text, metrics.densityDpi,
-                getScreenDensityBucket(metrics), width, height));
-    }
-
-    private static String getScreenDensityBucket(DisplayMetrics metrics) {
-        switch (metrics.densityDpi) {
-            case DisplayMetrics.DENSITY_LOW:
-                return "ldpi";
-
-            case DisplayMetrics.DENSITY_MEDIUM:
-                return "mdpi";
-
-            case DisplayMetrics.DENSITY_HIGH:
-                return "hdpi";
-
-            case DisplayMetrics.DENSITY_XHIGH:
-                return "xhdpi";
-
-            case DisplayMetrics.DENSITY_360:
-                return "360dpi";
-
-            case DisplayMetrics.DENSITY_400:
-                return "400dpi";
-
-            case DisplayMetrics.DENSITY_420:
-                return "420dpi";
-
-            case DisplayMetrics.DENSITY_XXHIGH:
-                return "xxhdpi";
-
-            case DisplayMetrics.DENSITY_560:
-                return "560dpi";
-
-            case DisplayMetrics.DENSITY_XXXHIGH:
-                return "xxxhdpi";
-
-            case DisplayMetrics.DENSITY_TV:
-                return "tvdpi";
-
-            default:
-                return "" + metrics.densityDpi;
-        }
-    }
-}
diff --git a/hostsidetests/theme/app/src/android/theme/app/ThemeDeviceActivity.java b/hostsidetests/theme/app/src/android/theme/app/ThemeDeviceActivity.java
index 6235fdf..78dc3d4 100644
--- a/hostsidetests/theme/app/src/android/theme/app/ThemeDeviceActivity.java
+++ b/hostsidetests/theme/app/src/android/theme/app/ThemeDeviceActivity.java
@@ -377,7 +377,6 @@
             new Layout(R.layout.color_red_light, "color_red_light"),
             new Layout(R.layout.datepicker, "datepicker",
                     new DatePickerModifier()),
-            new Layout(R.layout.display_info, "display_info"),
             new Layout(R.layout.edittext, "edittext"),
             new Layout(R.layout.progressbar_horizontal_0, "progressbar_horizontal_0"),
             new Layout(R.layout.progressbar_horizontal_100, "progressbar_horizontal_100"),
diff --git a/hostsidetests/theme/assets/24/420dpi.zip b/hostsidetests/theme/assets/24/420dpi.zip
new file mode 100644
index 0000000..1c380ff
--- /dev/null
+++ b/hostsidetests/theme/assets/24/420dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/24/560dpi.zip b/hostsidetests/theme/assets/24/560dpi.zip
new file mode 100644
index 0000000..a171f7c
--- /dev/null
+++ b/hostsidetests/theme/assets/24/560dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/24/xhdpi.zip b/hostsidetests/theme/assets/24/xhdpi.zip
new file mode 100644
index 0000000..96d4ae0
--- /dev/null
+++ b/hostsidetests/theme/assets/24/xhdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/run_theme_capture_device.py b/hostsidetests/theme/run_theme_capture_device.py
index 23171db..8d4c72f 100755
--- a/hostsidetests/theme/run_theme_capture_device.py
+++ b/hostsidetests/theme/run_theme_capture_device.py
@@ -19,6 +19,7 @@
 import sys
 import threading
 import time
+import traceback
 import Queue
 sys.path.append(sys.path[0])
 from android_device import *
@@ -29,9 +30,7 @@
     213 : "tvdpi",
     240 : "hdpi",
     320 : "xhdpi",
-    400 : "400dpi",
     480 : "xxhdpi",
-    560 : "560dpi",
     640 : "xxxhdpi",
 }
 
@@ -60,6 +59,7 @@
                             raise
                         except:
                             print "Failed to execute thread:", sys.exc_info()[0]
+                            traceback.print_exc()
                     q.task_done()
             except KeyboardInterrupt:
                 raise
@@ -97,17 +97,19 @@
     print "Found device: " + deviceSerial
     device = androidDevice(deviceSerial)
 
-    # outPath = outPath + "/%d/" % (device.getSdkLevel()) + deviceSerial
     outPath = outPath + "/%d" % (device.getSdkLevel())
     density = device.getDensity()
-    resName = CTS_THEME_dict[density]
+    if CTS_THEME_dict.has_key(density):
+        resName = CTS_THEME_dict[density]
+    else:
+        resName = str(density) + "dpi"
 
     device.uninstallApk("android.theme.app")
 
     (out, err, success) = device.installApk(themeApkPath)
     if not success:
         print "Failed to install APK on " + deviceSerial
-        printAdbResult(device, out, err)
+        printAdbResult(deviceSerial, out, err)
         return False
 
     print "Generating images on " + deviceSerial + "..."
diff --git a/hostsidetests/tv/src/com/android/cts/tv/TvInputManagerHostTest.java b/hostsidetests/tv/src/com/android/cts/tv/TvInputManagerHostTest.java
index 65a7928..fe917ae 100644
--- a/hostsidetests/tv/src/com/android/cts/tv/TvInputManagerHostTest.java
+++ b/hostsidetests/tv/src/com/android/cts/tv/TvInputManagerHostTest.java
@@ -106,16 +106,25 @@
         device.executeShellCommand(START_COMMAND);
         // Re-install the input app so the monitoring app can get the onInputUpdated callback.
         installPackage(TEST_APK);
-        String logs = device.executeAdbCommand("logcat", "-v", "brief", "-d", CLASS + ":I", "*:S");
         String testString = "";
-        Scanner in = new Scanner(logs);
-        while (in.hasNextLine()) {
-            String line = in.nextLine();
-            if(line.contains(INPUT_UPDATED_STRING)) {
-                testString = line.split(":")[1].trim();
+        for (int i = 0; i < 5; ++i) {
+            // Try 5 times as this sometimes fails.
+            String logs = device.executeAdbCommand(
+                    "logcat", "-v", "brief", "-d", CLASS + ":I", "*:S");
+            Scanner in = new Scanner(logs);
+            while (in.hasNextLine()) {
+                String line = in.nextLine();
+                if (line.contains(INPUT_UPDATED_STRING)) {
+                    testString = line.split(":")[1].trim();
+                }
             }
+            in.close();
+            if (!testString.isEmpty()) {
+                break;
+            }
+            // Wait for the system service to handle the installation.
+            Thread.currentThread().sleep(100);
         }
-        in.close();
         assertEquals("Incorrect test string", INPUT_UPDATED_STRING, testString);
     }
 
diff --git a/hostsidetests/ui/src/android/ui/cts/InstallTimeTest.java b/hostsidetests/ui/src/android/ui/cts/InstallTimeTest.java
index d91944a..5acb17e 100644
--- a/hostsidetests/ui/src/android/ui/cts/InstallTimeTest.java
+++ b/hostsidetests/ui/src/android/ui/cts/InstallTimeTest.java
@@ -44,6 +44,7 @@
     private IAbi mAbi;
 
     private static final String TAG = "InstallTimeTest";
+    private static final String REPORT_LOG_NAME = "CtsUiHostTestCases";
     static final String PACKAGE = "com.replica.replicaisland";
     static final String APK = "com.replica.replicaisland.apk";
     private static final double OUTLIER_THRESHOLD = 0.1;
@@ -72,8 +73,10 @@
     }
 
     public void testInstallTime() throws Exception {
+        String streamName = "test_install_time";
         MetricsReportLog report = new MetricsReportLog(mDevice.getSerialNumber(), mAbi.getName(),
-                String.format("%s#%s", getClass().getName(), "testInstallTime"));
+                String.format("%s#%s", getClass().getName(), "testInstallTime"), REPORT_LOG_NAME,
+                streamName);
         final int NUMBER_REPEAT = 10;
         final IBuildInfo build = mBuild;
         final ITestDevice device = mDevice;
@@ -89,12 +92,13 @@
                 device.installPackage(app, false, options);
             }
         });
-        report.addValues("install time", result, ResultType.LOWER_BETTER, ResultUnit.MS);
+        report.addValues("install_time", result, ResultType.LOWER_BETTER, ResultUnit.MS);
         Stat.StatResult stat = Stat.getStatWithOutlierRejection(result, OUTLIER_THRESHOLD);
         if (stat.mDataCount != result.length) {
             Log.w(TAG, "rejecting " + (result.length - stat.mDataCount) + " outliers");
         }
-        report.setSummary("install time", stat.mAverage, ResultType.LOWER_BETTER, ResultUnit.MS);
+        report.setSummary("install_time_average", stat.mAverage, ResultType.LOWER_BETTER,
+                ResultUnit.MS);
         report.submit();
     }
 
diff --git a/libs/deviceutil/src/android/cts/util/MediaUtils.java b/libs/deviceutil/src/android/cts/util/MediaUtils.java
index 7ab806d..a87715d 100755
--- a/libs/deviceutil/src/android/cts/util/MediaUtils.java
+++ b/libs/deviceutil/src/android/cts/util/MediaUtils.java
@@ -35,6 +35,8 @@
 import static java.lang.reflect.Modifier.isPublic;
 import static java.lang.reflect.Modifier.isStatic;
 import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Map;
 import android.util.Log;
 
@@ -461,6 +463,29 @@
         return extractor;
     }
 
+    /**
+     * return mime type of the resourceId
+     */
+    public static Collection<String> getDecodersForFormat(MediaFormat format) {
+        ArrayList<String> decoders = new ArrayList<String>();
+        String mime = format.getString(MediaFormat.KEY_MIME);
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        for (MediaCodecInfo info : mcl.getCodecInfos()) {
+            if (info.isEncoder()) {
+                continue;
+            }
+            CodecCapabilities caps = null;
+            try {
+                caps = info.getCapabilitiesForType(mime);
+            } catch (IllegalArgumentException e) {  // mime is not supported
+                continue;
+            }
+            if (caps.isFormatSupported(format))
+                decoders.add(info.getName());
+        }
+        return decoders;
+    }
+
     /*
      *  ------------------ HELPER METHODS FOR STATISTICS AND REPORTING ------------------
      */
diff --git a/tests/admin/Android.mk b/tests/admin/Android.mk
index e12cc94..60b7aeb 100644
--- a/tests/admin/Android.mk
+++ b/tests/admin/Android.mk
@@ -21,7 +21,7 @@
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
-    ctstestrunner mockito-target platform-test-annotations
+    ctstestrunner mockito-target
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java b/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
index 68c30ff..5dca2cc 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
@@ -2563,7 +2563,7 @@
             // If unable to find a preview size, then log the failure, and skip this run.
             if (previewSz == null) {
                 if (mStaticInfo.isCapabilitySupported(
-                    CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) {
+                        CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) {
                     mCollector.addMessage(String.format(
                             "Unable to find a preview size supporting given fps range %s",
                             fpsRange));
diff --git a/tests/camera/src/android/hardware/camera2/cts/DngCreatorTest.java b/tests/camera/src/android/hardware/camera2/cts/DngCreatorTest.java
index 02bb348..6ab55b0 100644
--- a/tests/camera/src/android/hardware/camera2/cts/DngCreatorTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/DngCreatorTest.java
@@ -591,7 +591,7 @@
                 Log.e(TAG, "Failed RAW patch file for camera " + deviceId + " saved to " +
                         rawFilePath);
 
-                fail("Camera " + mCamera.getId() + ": RAW and JPEG image at  for the same " +
+                fail("Camera " + deviceId + ": RAW and JPEG image at  for the same " +
                         "frame are not similar, center patches have difference metric of " +
                         difference);
             } finally {
diff --git a/tests/camera/src/android/hardware/camera2/cts/PerformanceTest.java b/tests/camera/src/android/hardware/camera2/cts/PerformanceTest.java
index 5c97d5c..7e28403 100644
--- a/tests/camera/src/android/hardware/camera2/cts/PerformanceTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/PerformanceTest.java
@@ -61,6 +61,7 @@
  */
 public class PerformanceTest extends Camera2SurfaceViewTestCase {
     private static final String TAG = "PerformanceTest";
+    private static final String REPORT_LOG_NAME = "CtsCameraTestCases";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
     private static final int NUM_TEST_LOOPS = 5;
     private static final int NUM_MAX_IMAGES = 4;
@@ -88,14 +89,11 @@
 
     @Override
     protected void setUp() throws Exception {
-        mReportLog = new DeviceReportLog();
         super.setUp();
     }
 
     @Override
     protected void tearDown() throws Exception {
-        // Deliver the report to host will automatically clear the report log.
-        mReportLog.submit(getInstrumentation());
         super.tearDown();
     }
 
@@ -120,6 +118,9 @@
         for (String id : mCameraIds) {
             // Do NOT move these variables to outer scope
             // They will be passed to DeviceReportLog and their references will be stored
+            String streamName = "test_camera_launch";
+            mReportLog = new DeviceReportLog(REPORT_LOG_NAME, streamName);
+            mReportLog.addValue("camera_id", id, ResultType.NEUTRAL, ResultUnit.NONE);
             double[] cameraOpenTimes = new double[NUM_TEST_LOOPS];
             double[] configureStreamTimes = new double[NUM_TEST_LOOPS];
             double[] startPreviewTimes = new double[NUM_TEST_LOOPS];
@@ -182,33 +183,32 @@
 
                 avgCameraLaunchTimes[counter] = Stat.getAverage(cameraLaunchTimes);
                 // Finish the data collection, report the KPIs.
-                mReportLog.addValues("Camera " + id
-                        + ": Camera open time", cameraOpenTimes,
+                // ReportLog keys have to be lowercase underscored format.
+                mReportLog.addValues("camera_open_time", cameraOpenTimes, ResultType.LOWER_BETTER,
+                        ResultUnit.MS);
+                mReportLog.addValues("camera_configure_stream_time", configureStreamTimes,
                         ResultType.LOWER_BETTER, ResultUnit.MS);
-                mReportLog.addValues("Camera " + id
-                        + ": Camera configure stream time", configureStreamTimes,
+                mReportLog.addValues("camera_start_preview_time", startPreviewTimes,
                         ResultType.LOWER_BETTER, ResultUnit.MS);
-                mReportLog.addValues("Camera " + id
-                        + ": Camera start preview time", startPreviewTimes,
+                mReportLog.addValues("camera_camera_stop_preview", stopPreviewTimes,
                         ResultType.LOWER_BETTER, ResultUnit.MS);
-                mReportLog.addValues("Camera " + id
-                        + ": Camera stop preview", stopPreviewTimes,
+                mReportLog.addValues("camera_camera_close_time", cameraCloseTimes,
                         ResultType.LOWER_BETTER, ResultUnit.MS);
-                mReportLog.addValues("Camera " + id
-                        + ": Camera close time", cameraCloseTimes,
-                        ResultType.LOWER_BETTER, ResultUnit.MS);
-                mReportLog.addValues("Camera " + id
-                        + ": Camera launch time", cameraLaunchTimes,
+                mReportLog.addValues("camera_launch_time", cameraLaunchTimes,
                         ResultType.LOWER_BETTER, ResultUnit.MS);
             }
             finally {
                 closeImageReader();
             }
             counter++;
+            mReportLog.submit(getInstrumentation());
         }
         if (mCameraIds.length != 0) {
-            mReportLog.setSummary("Camera launch average time for all cameras ",
+            String streamName = "test_camera_launch_average";
+            mReportLog = new DeviceReportLog(REPORT_LOG_NAME, streamName);
+            mReportLog.setSummary("camera_launch_average_time_for_all_cameras",
                     Stat.getAverage(avgCameraLaunchTimes), ResultType.LOWER_BETTER, ResultUnit.MS);
+            mReportLog.submit(getInstrumentation());
         }
     }
 
@@ -232,6 +232,9 @@
         for (String id : mCameraIds) {
             // Do NOT move these variables to outer scope
             // They will be passed to DeviceReportLog and their references will be stored
+            String streamName = "test_single_capture";
+            mReportLog = new DeviceReportLog(REPORT_LOG_NAME, streamName);
+            mReportLog.addValue("camera_id", id, ResultType.NEUTRAL, ResultUnit.NONE);
             double[] captureTimes = new double[NUM_TEST_LOOPS];
             double[] getPartialTimes = new double[NUM_TEST_LOOPS];
             double[] getResultTimes = new double[NUM_TEST_LOOPS];
@@ -301,20 +304,17 @@
                     // simulate real scenario (preview runs a bit)
                     waitForNumResults(previewResultListener, NUM_RESULTS_WAIT);
 
-                    stopPreviewAndDrain();
+                    stopPreview();
 
                 }
-                mReportLog.addValues("Camera " + id
-                        + ": Camera capture latency", captureTimes,
+                mReportLog.addValues("camera_capture_latency", captureTimes,
                         ResultType.LOWER_BETTER, ResultUnit.MS);
                 // If any of the partial results do not contain AE and AF state, then no report
                 if (isPartialTimingValid) {
-                    mReportLog.addValues("Camera " + id
-                            + ": Camera partial result latency", getPartialTimes,
+                    mReportLog.addValues("camera_partial_result_latency", getPartialTimes,
                             ResultType.LOWER_BETTER, ResultUnit.MS);
                 }
-                mReportLog.addValues("Camera " + id
-                        + ": Camera capture result latency", getResultTimes,
+                mReportLog.addValues("camera_capture_result_latency", getResultTimes,
                         ResultType.LOWER_BETTER, ResultUnit.MS);
 
                 avgResultTimes[counter] = Stat.getAverage(getResultTimes);
@@ -324,12 +324,16 @@
                 closeDevice();
             }
             counter++;
+            mReportLog.submit(getInstrumentation());
         }
 
         // Result will not be reported in CTS report if no summary is printed.
         if (mCameraIds.length != 0) {
-            mReportLog.setSummary("Camera capture result average latency for all cameras ",
+            String streamName = "test_single_capture_average";
+            mReportLog = new DeviceReportLog(REPORT_LOG_NAME, streamName);
+            mReportLog.setSummary("camera_capture_result_average_latency_for_all_cameras",
                     Stat.getAverage(avgResultTimes), ResultType.LOWER_BETTER, ResultUnit.MS);
+            mReportLog.submit(getInstrumentation());
         }
     }
 
@@ -346,12 +350,16 @@
 
                 try {
                     openDevice(id);
-
+                    String streamName = "test_reprocessing_latency";
+                    mReportLog = new DeviceReportLog(REPORT_LOG_NAME, streamName);
+                    mReportLog.addValue("camera_id", id, ResultType.NEUTRAL, ResultUnit.NONE);
+                    mReportLog.addValue("format", format, ResultType.NEUTRAL, ResultUnit.NONE);
                     reprocessingPerformanceTestByCamera(format, /*asyncMode*/false,
                             /*highQuality*/false);
                 } finally {
                     closeReaderWriters();
                     closeDevice();
+                    mReportLog.submit(getInstrumentation());
                 }
             }
         }
@@ -371,12 +379,16 @@
 
                 try {
                     openDevice(id);
-
+                    String streamName = "test_reprocessing_throughput";
+                    mReportLog = new DeviceReportLog(REPORT_LOG_NAME, streamName);
+                    mReportLog.addValue("camera_id", id, ResultType.NEUTRAL, ResultUnit.NONE);
+                    mReportLog.addValue("format", format, ResultType.NEUTRAL, ResultUnit.NONE);
                     reprocessingPerformanceTestByCamera(format, /*asyncMode*/true,
                             /*highQuality*/false);
                 } finally {
                     closeReaderWriters();
                     closeDevice();
+                    mReportLog.submit(getInstrumentation());
                 }
             }
         }
@@ -395,12 +407,16 @@
 
                 try {
                     openDevice(id);
-
+                    String streamName = "test_high_quality_reprocessing_latency";
+                    mReportLog = new DeviceReportLog(REPORT_LOG_NAME, streamName);
+                    mReportLog.addValue("camera_id", id, ResultType.NEUTRAL, ResultUnit.NONE);
+                    mReportLog.addValue("format", format, ResultType.NEUTRAL, ResultUnit.NONE);
                     reprocessingPerformanceTestByCamera(format, /*asyncMode*/false,
                             /*requireHighQuality*/true);
                 } finally {
                     closeReaderWriters();
                     closeDevice();
+                    mReportLog.submit(getInstrumentation());
                 }
             }
         }
@@ -420,12 +436,16 @@
 
                 try {
                     openDevice(id);
-
+                    String streamName = "test_high_quality_reprocessing_throughput";
+                    mReportLog = new DeviceReportLog(REPORT_LOG_NAME, streamName);
+                    mReportLog.addValue("camera_id", id, ResultType.NEUTRAL, ResultUnit.NONE);
+                    mReportLog.addValue("format", format, ResultType.NEUTRAL, ResultUnit.NONE);
                     reprocessingPerformanceTestByCamera(format, /*asyncMode*/true,
                             /*requireHighQuality*/true);
                 } finally {
                     closeReaderWriters();
                     closeDevice();
+                    mReportLog.submit(getInstrumentation());
                 }
             }
         }
@@ -443,11 +463,15 @@
 
                 try {
                     openDevice(id);
-
+                    String streamName = "test_reprocessing_capture_stall";
+                    mReportLog = new DeviceReportLog(REPORT_LOG_NAME, streamName);
+                    mReportLog.addValue("camera_id", id, ResultType.NEUTRAL, ResultUnit.NONE);
+                    mReportLog.addValue("format", format, ResultType.NEUTRAL, ResultUnit.NONE);
                     reprocessingCaptureStallTestByCamera(format);
                 } finally {
                     closeReaderWriters();
                     closeDevice();
+                    mReportLog.submit(getInstrumentation());
                 }
             }
         }
@@ -534,20 +558,17 @@
 
         stopZslStreaming();
 
-        String reprocessType = " YUV reprocessing ";
+        String reprocessType = "YUV reprocessing";
         if (reprocessInputFormat == ImageFormat.PRIVATE) {
-            reprocessType = " opaque reprocessing ";
+            reprocessType = "opaque reprocessing";
         }
-
-        mReportLog.addValues("Camera " + mCamera.getId()
-                + ":" + reprocessType + " max capture timestamp gaps", maxCaptureGapsMs,
+        mReportLog.addValue("reprocess_type", reprocessType, ResultType.NEUTRAL, ResultUnit.NONE);
+        mReportLog.addValues("max_capture_timestamp_gaps", maxCaptureGapsMs,
                 ResultType.LOWER_BETTER, ResultUnit.MS);
-        mReportLog.addValues("Camera " + mCamera.getId()
-                + ":" + reprocessType + "capture average frame duration", averageFrameDurationMs,
+        mReportLog.addValues("capture_average_frame_duration", averageFrameDurationMs,
                 ResultType.LOWER_BETTER, ResultUnit.MS);
-        mReportLog.setSummary("Camera reprocessing average max capture timestamp gaps for Camera "
-                + mCamera.getId(), Stat.getAverage(maxCaptureGapsMs), ResultType.LOWER_BETTER,
-                ResultUnit.MS);
+        mReportLog.setSummary("camera_reprocessing_average_max_capture_timestamp_gaps",
+                Stat.getAverage(maxCaptureGapsMs), ResultType.LOWER_BETTER, ResultUnit.MS);
 
         // The max timestamp gap should be less than (captureStall + 1) x average frame
         // duration * (1 + error margin).
@@ -634,9 +655,9 @@
 
         stopZslStreaming();
 
-        String reprocessType = " YUV reprocessing ";
+        String reprocessType = "YUV reprocessing";
         if (reprocessInputFormat == ImageFormat.PRIVATE) {
-            reprocessType = " opaque reprocessing ";
+            reprocessType = "opaque reprocessing";
         }
 
         // Report the performance data
@@ -646,23 +667,27 @@
             if (requireHighQuality) {
                 captureMsg += " for High Quality noise reduction and edge modes";
             }
-            mReportLog.addValues("Camera " + mCamera.getId()
-                    + ":" + reprocessType + captureMsg, getImageLatenciesMs,
-                    ResultType.LOWER_BETTER, ResultUnit.MS);
-            mReportLog.setSummary("Camera reprocessing average latency for Camera " +
-                    mCamera.getId(), Stat.getAverage(getImageLatenciesMs), ResultType.LOWER_BETTER,
+            mReportLog.addValue("reprocess_type", reprocessType, ResultType.NEUTRAL,
+                    ResultUnit.NONE);
+            mReportLog.addValue("capture_message", captureMsg, ResultType.NEUTRAL,
+                    ResultUnit.NONE);
+            mReportLog.addValues("latency", getImageLatenciesMs, ResultType.LOWER_BETTER,
                     ResultUnit.MS);
+            mReportLog.setSummary("camera_reprocessing_average_latency",
+                    Stat.getAverage(getImageLatenciesMs), ResultType.LOWER_BETTER, ResultUnit.MS);
         } else {
             captureMsg = "shot to shot latency";
             if (requireHighQuality) {
                 captureMsg += " for High Quality noise reduction and edge modes";
             }
-            mReportLog.addValues("Camera " + mCamera.getId()
-                    + ":" + reprocessType + captureMsg, getImageLatenciesMs,
-                    ResultType.LOWER_BETTER, ResultUnit.MS);
-            mReportLog.setSummary("Camera reprocessing shot to shot average latency for Camera " +
-                    mCamera.getId(), Stat.getAverage(getImageLatenciesMs), ResultType.LOWER_BETTER,
+            mReportLog.addValue("reprocess_type", reprocessType, ResultType.NEUTRAL,
+                    ResultUnit.NONE);
+            mReportLog.addValue("capture_message", captureMsg, ResultType.NEUTRAL,
+                    ResultUnit.NONE);
+            mReportLog.addValues("latency", getImageLatenciesMs, ResultType.LOWER_BETTER,
                     ResultUnit.MS);
+            mReportLog.setSummary("camera_reprocessing_shot_to_shot_average_latency",
+                    Stat.getAverage(getImageLatenciesMs), ResultType.LOWER_BETTER, ResultUnit.MS);
         }
     }
 
diff --git a/tests/camera/src/android/hardware/camera2/cts/helpers/StaticMetadata.java b/tests/camera/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
index 5736ebd..df89297 100644
--- a/tests/camera/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
+++ b/tests/camera/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
@@ -2097,7 +2097,7 @@
 
         checkTrueForKey(key, " value is out of range ",
                 facing >= CameraCharacteristics.LENS_FACING_FRONT &&
-                facing <= CameraCharacteristics.LENS_FACING_BACK);
+                facing <= CameraCharacteristics.LENS_FACING_EXTERNAL);
         return facing;
     }
 
diff --git a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2SurfaceViewTestCase.java b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2SurfaceViewTestCase.java
index 9ffe095..8d141c4 100644
--- a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2SurfaceViewTestCase.java
+++ b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2SurfaceViewTestCase.java
@@ -101,6 +101,7 @@
     protected Surface mPreviewSurface;
     protected Size mPreviewSize;
     protected List<Size> mOrderedPreviewSizes; // In descending order.
+    protected List<Size> m1080pBoundedOrderedPreviewSizes; // In descending order.
     protected List<Size> mOrderedVideoSizes; // In descending order.
     protected List<Size> mOrderedStillSizes; // In descending order.
     protected HashMap<Size, Long> mMinPreviewFrameDurationMap;
@@ -581,6 +582,8 @@
         if (mStaticInfo.isColorOutputSupported()) {
             mOrderedPreviewSizes = getSupportedPreviewSizes(cameraId, mCameraManager,
                     getPreviewSizeBound(mWindowManager, PREVIEW_SIZE_BOUND));
+            m1080pBoundedOrderedPreviewSizes = getSupportedPreviewSizes(cameraId, mCameraManager,
+                    PREVIEW_SIZE_BOUND);
             mOrderedVideoSizes = getSupportedVideoSizes(cameraId, mCameraManager, PREVIEW_SIZE_BOUND);
             mOrderedStillSizes = getSupportedStillSizes(cameraId, mCameraManager, null);
             // Use ImageFormat.YUV_420_888 for now. TODO: need figure out what's format for preview
@@ -749,6 +752,22 @@
             }
         }
 
+        // Search again for sizes not bounded by display size
+        for (Size size : m1080pBoundedOrderedPreviewSizes) {
+            Long minDuration = mMinPreviewFrameDurationMap.get(size);
+            if (minDuration == null ||
+                    minDuration == 0) {
+                if (mStaticInfo.isCapabilitySupported(
+                        CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) {
+                    throw new IllegalArgumentException(
+                            "No min frame duration available for the size " + size);
+                }
+                continue;
+            }
+            if (minDuration <= (frameDurationRange[0] + MIN_FRAME_DURATION_ERROR_MARGIN)) {
+                return size;
+            }
+        }
         return null;
     }
 
diff --git a/tests/core/runner/Android.mk b/tests/core/runner/Android.mk
index 1355512..f2b83ed 100644
--- a/tests/core/runner/Android.mk
+++ b/tests/core/runner/Android.mk
@@ -45,7 +45,8 @@
     compatibility-device-util \
     android-support-test \
     android.test.runner \
-    vogarexpect
+    vogarexpect \
+    testng
 
 LOCAL_JAVA_LANGUAGE_VERSION := 1.8
 
diff --git a/tests/core/runner/src/com/android/cts/core/internal/runner/TestLoader.java b/tests/core/runner/src/com/android/cts/core/internal/runner/TestLoader.java
new file mode 100644
index 0000000..108e8fd
--- /dev/null
+++ b/tests/core/runner/src/com/android/cts/core/internal/runner/TestLoader.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+/**
+ * This file is a copy of https://cs.corp.google.com/android/frameworks/testing/runner/src/main/java/android/support/test/internal/runner/TestLoader.java
+ * The only changes that have been made starts with // Libcore-specific
+ */
+
+package com.android.cts.core.internal.runner;
+
+import android.util.Log;
+
+import org.junit.runner.Description;
+import org.junit.runner.notification.Failure;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A class for loading JUnit3 and JUnit4 test classes given a set of potential class names.
+ */
+public final class TestLoader {
+
+    private static final String LOG_TAG = "TestLoader";
+    // Libcore-specific change: Fully qualified name of TestNG annotation class.
+    private static final String TESTNG_TEST = "org.testng.annotations.Test";
+
+    private Map<String, Class<?>> mLoadedClassesMap = new LinkedHashMap<String, Class<?>>();
+    private Map<String, Failure> mLoadFailuresMap = new LinkedHashMap<String, Failure>();
+
+    private ClassLoader mClassLoader;
+
+    /**
+     * Set the {@link ClassLoader} to be used to load test cases.
+     *
+     * @param loader {@link ClassLoader} to load test cases with.
+     */
+    public void setClassLoader(ClassLoader loader) {
+        mClassLoader = loader;
+    }
+
+    /**
+     * Loads the test class from a given class name if its not already loaded.
+     * <p/>
+     * Will store the result internally. Successfully loaded classes can be retrieved via
+     * {@link #getLoadedClasses()}, failures via {@link #getLoadFailures()}.
+     *
+     * @param className the class name to attempt to load
+     * @return the loaded class or null.
+     */
+    public Class<?> loadClass(String className) {
+        Class<?> loadedClass = doLoadClass(className);
+        if (loadedClass != null) {
+            mLoadedClassesMap.put(className, loadedClass);
+        }
+        return loadedClass;
+    }
+
+    protected ClassLoader getClassLoader() {
+        if (mClassLoader != null) {
+            return mClassLoader;
+        }
+
+        // TODO: InstrumentationTestRunner uses
+        // Class.forName(className, false, getTargetContext().getClassLoader());
+        // Evaluate if that is needed. Initial testing indicates
+        // getTargetContext().getClassLoader() == this.getClass().getClassLoader()
+        return this.getClass().getClassLoader();
+    }
+
+    private Class<?> doLoadClass(String className) {
+        if (mLoadFailuresMap.containsKey(className)) {
+            // Don't load classes that already failed to load
+            return null;
+        } else if (mLoadedClassesMap.containsKey(className)) {
+            // Class with the same name was already loaded, return it
+            return mLoadedClassesMap.get(className);
+        }
+
+        try {
+            ClassLoader myClassLoader = getClassLoader();
+            return Class.forName(className, false, myClassLoader);
+        } catch (ClassNotFoundException e) {
+            String errMsg = String.format("Could not find class: %s", className);
+            Log.e(LOG_TAG, errMsg);
+            Description description = Description.createSuiteDescription(className);
+            Failure failure = new Failure(description, e);
+            mLoadFailuresMap.put(className, failure);
+        }
+        return null;
+    }
+
+    /**
+     * Loads the test class from the given class name.
+     * <p/>
+     * Similar to {@link #loadClass(String)}, but will ignore classes that are
+     * not tests.
+     *
+     * @param className the class name to attempt to load
+     * @return the loaded class or null.
+     */
+    public Class<?> loadIfTest(String className) {
+        Class<?> loadedClass = doLoadClass(className);
+        if (loadedClass != null && isTestClass(loadedClass)) {
+            mLoadedClassesMap.put(className, loadedClass);
+            return loadedClass;
+        }
+        return null;
+    }
+
+    /**
+     * @return whether this {@link TestLoader} contains any loaded classes or load failures.
+     */
+    public boolean isEmpty() {
+        return mLoadedClassesMap.isEmpty() && mLoadFailuresMap.isEmpty();
+    }
+
+    /**
+     * Get the {@link Collection) of classes successfully loaded via
+     * {@link #loadIfTest(String)} calls.
+     */
+    public Collection<Class<?>> getLoadedClasses() {
+        return mLoadedClassesMap.values();
+    }
+
+    /**
+     * Get the {@link List) of {@link Failure} that occurred during
+     * {@link #loadIfTest(String)} calls.
+     */
+    public Collection<Failure> getLoadFailures() {
+        return mLoadFailuresMap.values();
+    }
+
+    /**
+     * Determines if given class is a valid test class.
+     *
+     * @param loadedClass
+     * @return <code>true</code> if loadedClass is a test
+     */
+    private boolean isTestClass(Class<?> loadedClass) {
+        try {
+            if (Modifier.isAbstract(loadedClass.getModifiers())) {
+                logDebug(String.format("Skipping abstract class %s: not a test",
+                        loadedClass.getName()));
+                return false;
+            }
+            // Libcore-specific change: Also consider TestNG annotated classes.
+            if (isTestNgTestClass(loadedClass)) {
+              return true;
+            }
+            // TODO: try to find upstream junit calls to replace these checks
+            if (junit.framework.Test.class.isAssignableFrom(loadedClass)) {
+                // ensure that if a TestCase, it has at least one test method otherwise
+                // TestSuite will throw error
+                if (junit.framework.TestCase.class.isAssignableFrom(loadedClass)) {
+                    return hasJUnit3TestMethod(loadedClass);
+                }
+                return true;
+            }
+            // TODO: look for a 'suite' method?
+            if (loadedClass.isAnnotationPresent(org.junit.runner.RunWith.class)) {
+                return true;
+            }
+            for (Method testMethod : loadedClass.getMethods()) {
+                if (testMethod.isAnnotationPresent(org.junit.Test.class)) {
+                    return true;
+                }
+            }
+            logDebug(String.format("Skipping class %s: not a test", loadedClass.getName()));
+            return false;
+        } catch (Exception e) {
+            // Defensively catch exceptions - Will throw runtime exception if it cannot load methods.
+            // For earlier versions of Android (Pre-ICS), Dalvik might try to initialize a class
+            // during getMethods(), fail to do so, hide the error and throw a NoSuchMethodException.
+            // Since the java.lang.Class.getMethods does not declare such an exception, resort to a
+            // generic catch all.
+            // For ICS+, Dalvik will throw a NoClassDefFoundException.
+            Log.w(LOG_TAG, String.format("%s in isTestClass for %s", e.toString(),
+                    loadedClass.getName()));
+            return false;
+        } catch (Error e) {
+            // defensively catch Errors too
+            Log.w(LOG_TAG, String.format("%s in isTestClass for %s", e.toString(),
+                    loadedClass.getName()));
+            return false;
+        }
+    }
+
+    private boolean hasJUnit3TestMethod(Class<?> loadedClass) {
+        for (Method testMethod : loadedClass.getMethods()) {
+            if (isPublicTestMethod(testMethod)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    // copied from junit.framework.TestSuite
+    private boolean isPublicTestMethod(Method m) {
+        return isTestMethod(m) && Modifier.isPublic(m.getModifiers());
+    }
+
+    // copied from junit.framework.TestSuite
+    private boolean isTestMethod(Method m) {
+        return m.getParameterTypes().length == 0 && m.getName().startsWith("test")
+                && m.getReturnType().equals(Void.TYPE);
+    }
+
+    // Libcore-specific change: Add method for checking TestNG-annotated classes.
+    private static boolean isTestNgTestClass(Class<?> cls) {
+      // TestNG test is either marked @Test at the class
+      for (Annotation a : cls.getAnnotations()) {
+          if (a.annotationType().getName().equals(TESTNG_TEST)) {
+              return true;
+          }
+      }
+
+      // Or It's marked @Test at the method level
+      for (Method m : cls.getDeclaredMethods()) {
+        for (Annotation a : m.getAnnotations()) {
+          if (a.annotationType().getName().equals(TESTNG_TEST)) {
+              return true;
+          }
+        }
+      }
+
+      return false;
+    }
+
+
+    /**
+     * Utility method for logging debug messages. Only actually logs a message if LOG_TAG is marked
+     * as loggable to limit log spam during normal use.
+     */
+    private void logDebug(String msg) {
+        if (Log.isLoggable(LOG_TAG, Log.DEBUG)) {
+            Log.d(LOG_TAG, msg);
+        }
+    }
+}
diff --git a/tests/core/runner/src/com/android/cts/core/runner/AndroidJUnitRunnerConstants.java b/tests/core/runner/src/com/android/cts/core/runner/AndroidJUnitRunnerConstants.java
index 8bc2f01..4b18cdc 100644
--- a/tests/core/runner/src/com/android/cts/core/runner/AndroidJUnitRunnerConstants.java
+++ b/tests/core/runner/src/com/android/cts/core/runner/AndroidJUnitRunnerConstants.java
@@ -24,7 +24,7 @@
 public interface AndroidJUnitRunnerConstants {
 
     /**
-     * The names of the file containing the names of the tests to run.
+     * The name of the file containing the names of the tests to run.
      *
      * <p>This is an internal constant used within
      * {@code android.support.test.internal.runner.RunnerArgs}, which is used on both the server
@@ -39,6 +39,21 @@
     String ARGUMENT_TEST_FILE = "testFile";
 
     /**
+     * The name of the file containing the names of the tests not to run.
+     *
+     * <p>This is an internal constant used within
+     * {@code android.support.test.internal.runner.RunnerArgs}, which is used on both the server
+     * and
+     * client side. The constant is used when there are too many test names to pass on the command
+     * line, in which case they are stored in a file that is pushed to the device and then the
+     * location of that file is passed in this argument. The {@code RunnerArgs} on the client will
+     * read the contents of that file in order to retrieve the list of names and then return that
+     * to
+     * its client without the client being aware of how that was done.
+     */
+    String ARGUMENT_NOT_TEST_FILE = "notTestFile";
+
+    /**
      * A comma separated list of the names of test classes to run.
      *
      * <p>The equivalent constant in {@code InstrumentationTestRunner} is hidden and so not
diff --git a/tests/core/runner/src/com/android/cts/core/runner/CoreTestRunner.java b/tests/core/runner/src/com/android/cts/core/runner/CoreTestRunner.java
index 887762f..d995fd9 100644
--- a/tests/core/runner/src/com/android/cts/core/runner/CoreTestRunner.java
+++ b/tests/core/runner/src/com/android/cts/core/runner/CoreTestRunner.java
@@ -55,6 +55,7 @@
 import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_DEBUG;
 import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_LOG_ONLY;
 import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_NOT_TEST_CLASS;
+import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_NOT_TEST_FILE;
 import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_NOT_TEST_PACKAGE;
 import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_TEST_CLASS;
 import static com.android.cts.core.runner.AndroidJUnitRunnerConstants.ARGUMENT_TEST_FILE;
@@ -154,8 +155,18 @@
             testNameSet.addAll(Arrays.asList(tests));
         }
 
+        // Tests may be excluded from the run by passing a list of tests not to run,
+        // or by passing a fileName with a test not to run on each line.
         Set<String> notTestNameSet = new HashSet<>();
-        if ((arg = args.getString(ARGUMENT_NOT_TEST_CLASS)) != null) {
+        if ((arg = args.getString(ARGUMENT_NOT_TEST_FILE)) != null) {
+            // The tests are specified in a file.
+            try {
+                notTestNameSet.addAll(readTestsFromFile(arg));
+            } catch (IOException err) {
+                finish(Activity.RESULT_CANCELED, new Bundle());
+                return;
+            }
+        } else if ((arg = args.getString(ARGUMENT_NOT_TEST_CLASS)) != null) {
             // The classes are specified in a String passed in the bundle
             String[] tests = arg.split(",");
             notTestNameSet.addAll(Arrays.asList(tests));
@@ -241,6 +252,9 @@
         try {
             RunnerBuilder runnerBuilder = new ExtendedAndroidRunnerBuilder(runnerParams);
             Class[] classes = testList.getClassesToRun();
+            for (Class cls : classes) {
+              Log.d(TAG, "Found class to run: " + cls.getName());
+            }
             Runner suite = new Computer().getSuite(runnerBuilder, classes);
 
             if (suite instanceof Filterable) {
diff --git a/tests/core/runner/src/com/android/cts/core/runner/TestClassFinder.java b/tests/core/runner/src/com/android/cts/core/runner/TestClassFinder.java
index 1b15aec..6ad95f6 100644
--- a/tests/core/runner/src/com/android/cts/core/runner/TestClassFinder.java
+++ b/tests/core/runner/src/com/android/cts/core/runner/TestClassFinder.java
@@ -13,23 +13,32 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package com.android.cts.core.runner;
 
 import android.support.test.internal.runner.ClassPathScanner;
 import android.support.test.internal.runner.ClassPathScanner.ClassNameFilter;
-import android.support.test.internal.runner.TestLoader;
 import android.util.Log;
+
+import com.android.cts.core.internal.runner.TestLoader;
+
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Enumeration;
 import java.util.List;
 import java.util.Set;
 
+import dalvik.system.DexFile;
+
 /**
  * Find tests in the current APK.
  */
 public class TestClassFinder {
 
+    private static final String TAG = "TestClassFinder";
+    private static final boolean DEBUG = false;
+
     // Excluded test packages
     private static final String[] DEFAULT_EXCLUDED_PACKAGES = {
             "junit",
@@ -40,6 +49,36 @@
     };
 
     static Collection<Class<?>> getClasses(List<String> apks, ClassLoader loader) {
+        if (DEBUG) {
+          Log.d(TAG, "getClasses: =======================================");
+
+          for (String apkPath : apks) {
+            Log.d(TAG, "getClasses: -------------------------------");
+            Log.d(TAG, "getClasses: APK " + apkPath);
+
+            DexFile dexFile = null;
+            try {
+                dexFile = new DexFile(apkPath);
+                Enumeration<String> apkClassNames = dexFile.entries();
+                while (apkClassNames.hasMoreElements()) {
+                    String apkClassName = apkClassNames.nextElement();
+                    Log.d(TAG, "getClasses: DexClass element " + apkClassName);
+                }
+            } catch (IOException e) {
+              throw new AssertionError(e);
+            } finally {
+                if (dexFile != null) {
+                  try {
+                    dexFile.close();
+                  } catch (IOException e) {
+                    throw new AssertionError(e);
+                  }
+                }
+            }
+            Log.d(TAG, "getClasses: -------------------------------");
+          }
+        }  // if DEBUG
+
         List<Class<?>> classes = new ArrayList<>();
         ClassPathScanner scanner = new ClassPathScanner(apks);
 
@@ -53,15 +92,24 @@
             filter.add(new ExcludePackageNameFilter(defaultExcludedPackage));
         }
 
+        // exclude any classes that aren't a "test class" (see #loadIfTest)
         TestLoader testLoader = new TestLoader();
         testLoader.setClassLoader(loader);
 
         try {
             Set<String> classNames = scanner.getClassPathEntries(filter);
             for (String className : classNames) {
+                // Important: This further acts as an additional filter;
+                // classes that aren't a "test class" are never loaded.
                 Class<?> cls = testLoader.loadIfTest(className);
                 if (cls != null) {
                     classes.add(cls);
+
+                    if (DEBUG) {
+                      Log.d(TAG, "getClasses: Loaded " + className);
+                    }
+                } else if (DEBUG) {
+                  Log.d(TAG, "getClasses: Failed to load class " + className);
                 }
             }
             return classes;
@@ -69,6 +117,11 @@
             Log.e(CoreTestRunner.TAG, "Failed to scan classes", e);
         }
 
+
+        if (DEBUG) {
+            Log.d(TAG, "getClasses: =======================================");
+        }
+
         return testLoader.getLoadedClasses();
     }
 
diff --git a/tests/core/runner/src/com/android/cts/core/runner/support/ExtendedAndroidRunnerBuilder.java b/tests/core/runner/src/com/android/cts/core/runner/support/ExtendedAndroidRunnerBuilder.java
index 527f9fd..58ef951 100644
--- a/tests/core/runner/src/com/android/cts/core/runner/support/ExtendedAndroidRunnerBuilder.java
+++ b/tests/core/runner/src/com/android/cts/core/runner/support/ExtendedAndroidRunnerBuilder.java
@@ -13,10 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package com.android.cts.core.runner.support;
 
 import android.support.test.internal.runner.AndroidLogOnlyBuilder;
 import android.support.test.internal.util.AndroidRunnerParams;
+import android.util.Log;
 import org.junit.runners.model.RunnerBuilder;
 import org.junit.runner.Runner;
 
@@ -26,7 +28,10 @@
  */
 public class ExtendedAndroidRunnerBuilder extends AndroidRunnerBuilder {
 
-   private final ExtendedAndroidLogOnlyBuilder androidLogOnlyBuilder;
+    private final ExtendedAndroidLogOnlyBuilder androidLogOnlyBuilder;
+    private static final boolean DEBUG = false;
+
+    private final TestNgRunnerBuilder mTestNgBuilder;
 
     /**
      * @param runnerParams {@link AndroidRunnerParams} that stores common runner parameters
@@ -34,16 +39,42 @@
     public ExtendedAndroidRunnerBuilder(AndroidRunnerParams runnerParams) {
         super(runnerParams, false /* CTSv1 filtered out Test suite() classes. */);
         androidLogOnlyBuilder = new ExtendedAndroidLogOnlyBuilder(runnerParams);
+        mTestNgBuilder = new TestNgRunnerBuilder(runnerParams);
     }
 
     @Override
     public Runner runnerForClass(Class<?> testClass) throws Throwable {
-        // Check if this is a dry-run with -e log true argument passed to the runner.
-        Runner runner = androidLogOnlyBuilder.runnerForClass(testClass);
-        if (runner != null) {
-            return runner;
-        }
-        // Otherwise use the default behaviour
-        return super.runnerForClass(testClass);
+      if (DEBUG) {
+        Log.d("ExAndRunBuild", "runnerForClass: Searching runner for class " + testClass.getName());
+      }
+
+      Runner runner;
+      // Give TestNG tests a chance to participate in the Runner search first.
+      // (Note that the TestNG runner handles log-only runs by itself)
+      runner = mTestNgBuilder.runnerForClass(testClass);
+      if (runner != null) {
+        logFoundRunner(runner);
+        return runner;
+      }
+
+      // Check if this is a dry-run with -e log true argument passed to the runner.
+      runner = androidLogOnlyBuilder.runnerForClass(testClass);
+      if (runner != null) {
+        logFoundRunner(runner);
+        return runner;
+      }
+
+      // Use the normal Runner search mechanism (for Junit tests).
+      runner = super.runnerForClass(testClass);
+      logFoundRunner(runner);
+
+      return runner;
+    }
+
+    private static void logFoundRunner(Runner runner) {
+      if (DEBUG) {
+        Log.d("ExAndRunBuild", "runnerForClass: Found runner of type " +
+            ((runner == null) ? "<null>" : runner.getClass().getName()));
+      }
     }
 }
diff --git a/tests/core/runner/src/com/android/cts/core/runner/support/SingleTestNGTestRunListener.java b/tests/core/runner/src/com/android/cts/core/runner/support/SingleTestNGTestRunListener.java
new file mode 100644
index 0000000..5568d08
--- /dev/null
+++ b/tests/core/runner/src/com/android/cts/core/runner/support/SingleTestNGTestRunListener.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2016 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.cts.core.runner.support;
+
+import java.util.Arrays;
+
+/**
+ * Listener for TestNG runs that provides gtest-like console output.
+ *
+ * Prints a message like [RUN], [OK], [ERROR], [SKIP] to stdout
+ * as tests are being executed with their status.
+ *
+ * This output is also saved as the device logs (logcat) when the test is run through
+ * cts-tradefed.
+ */
+public class SingleTestNGTestRunListener implements org.testng.ITestListener {
+    private int mTestStarted = 0;
+
+    private static class Prefixes {
+        @SuppressWarnings("unused")
+        private static final String INFORMATIONAL_MARKER =  "[----------]";
+        private static final String START_TEST_MARKER =     "[ RUN      ]";
+        private static final String OK_TEST_MARKER =        "[       OK ]";
+        private static final String ERROR_TEST_RUN_MARKER = "[    ERROR ]";
+        private static final String SKIPPED_TEST_MARKER =   "[     SKIP ]";
+        private static final String TEST_RUN_MARKER =       "[==========]";
+    }
+
+    // How many tests did TestNG *actually* try to run?
+    public int getNumTestStarted() {
+      return mTestStarted;
+    }
+
+    @Override
+    public void onFinish(org.testng.ITestContext context) {
+        System.out.println(String.format("%s", Prefixes.TEST_RUN_MARKER));
+    }
+
+    @Override
+    public void onStart(org.testng.ITestContext context) {
+        System.out.println(String.format("%s", Prefixes.INFORMATIONAL_MARKER));
+    }
+
+    @Override
+    public void onTestFailedButWithinSuccessPercentage(org.testng.ITestResult result) {
+        onTestFailure(result);
+    }
+
+    @Override
+    public void onTestFailure(org.testng.ITestResult result) {
+        // All failures are coalesced into one '[ FAILED ]' message at the end
+        // This is because a single test method can run multiple times with different parameters.
+        // Since we only test a single method, it's safe to combine all failures into one
+        // failure at the end.
+        //
+        // The big pass/fail is printed from SingleTestNGTestRunner, not from the listener.
+        System.out.println(String.format("%s %s ::: %s", Prefixes.ERROR_TEST_RUN_MARKER,
+              getId(result), stringify(result.getThrowable())));
+    }
+
+    @Override
+    public void onTestSkipped(org.testng.ITestResult result) {
+        System.out.println(String.format("%s %s", Prefixes.SKIPPED_TEST_MARKER,
+              getId(result)));
+    }
+
+    @Override
+    public void onTestStart(org.testng.ITestResult result) {
+        mTestStarted++;
+        System.out.println(String.format("%s %s", Prefixes.START_TEST_MARKER,
+              getId(result)));
+    }
+
+    @Override
+    public void onTestSuccess(org.testng.ITestResult result) {
+        System.out.println(String.format("%s", Prefixes.OK_TEST_MARKER));
+    }
+
+    private String getId(org.testng.ITestResult test) {
+        // TestNG is quite complicated since tests can have arbitrary parameters.
+        // Use its code to stringify a result name instead of doing it ourselves.
+
+        org.testng.remote.strprotocol.TestResultMessage msg =
+                new org.testng.remote.strprotocol.TestResultMessage(
+                    null, /*suite name*/
+                    null, /*test name -- display the test method name instead */
+                    test);
+
+        String className = test.getTestClass().getName();
+        //String name = test.getMethod().getMethodName();
+        return String.format("%s#%s", className, msg.toDisplayString());
+
+    }
+
+    private String stringify(Throwable error) {
+        return Arrays.toString(error.getStackTrace()).replaceAll("\n", " ");
+    }
+}
diff --git a/tests/core/runner/src/com/android/cts/core/runner/support/SingleTestNgTestExecutor.java b/tests/core/runner/src/com/android/cts/core/runner/support/SingleTestNgTestExecutor.java
new file mode 100644
index 0000000..ccab7b0
--- /dev/null
+++ b/tests/core/runner/src/com/android/cts/core/runner/support/SingleTestNgTestExecutor.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2016 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.cts.core.runner.support;
+
+import android.util.Log;
+
+import org.testng.TestNG;
+import org.testng.xml.XmlClass;
+import org.testng.xml.XmlInclude;
+import org.testng.xml.XmlSuite;
+import org.testng.xml.XmlTest;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Test executor to run a single TestNG test method.
+ */
+public class SingleTestNgTestExecutor {
+    // Execute any method which is in the class klass.
+    // The klass is passed in separately to handle inherited methods only.
+    // Returns true if all tests pass, false otherwise.
+    public static boolean execute(Class<?> klass, String methodName) {
+        if (klass == null) {
+          throw new NullPointerException("klass must not be null");
+        }
+
+        if (methodName == null) {
+          throw new NullPointerException("methodName must not be null");
+        }
+
+        //if (!method.getDeclaringClass().isAssignableFrom(klass)) {
+        //  throw new IllegalArgumentException("klass must match method's declaring class");
+        //}
+
+        SingleTestNGTestRunListener listener = new SingleTestNGTestRunListener();
+
+        // Although creating a new testng "core" every time might seem heavyweight, in practice
+        // it seems to take a mere few milliseconds at most.
+        // Since we're running all the parameteric combinations of a test,
+        // this ends up being neglible relative to that.
+        TestNG testng = createTestNG(klass.getName(), methodName, listener);
+        testng.run();
+
+        if (listener.getNumTestStarted() <= 0) {
+          // It's possible to be invoked here with an arbitrary method name
+          // so print out a warning incase TestNG actually had a no-op.
+          Log.w("TestNgExec", "execute class " + klass.getName() + ", method " + methodName +
+              " had 0 tests executed. Not a test method?");
+        }
+
+        return !testng.hasFailure();
+    }
+
+    private static org.testng.TestNG createTestNG(String klass, String method,
+            SingleTestNGTestRunListener listener) {
+        org.testng.TestNG testng = new org.testng.TestNG();
+        testng.setUseDefaultListeners(false);  // Don't create the testng-specific HTML/XML reports.
+        // It still prints the X/Y tests succeeded/failed summary to stdout.
+
+        // We don't strictly need this listener for CTS, but having it print SUCCESS/FAIL
+        // makes it easier to diagnose which particular combination of a test method had failed
+        // from looking at device logcat.
+        testng.addListener(listener);
+
+        /* Construct the following equivalent XML configuration:
+         *
+         * <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
+         * <suite>
+         *   <test>
+         *     <classes>
+         *       <class name="$klass">
+         *         <include name="$method" />
+         *       </class>
+         *     </classes>
+         *   </test>
+         * </suite>
+         *
+         * This will ensure that only a single klass/method is being run by testng.
+         * (It can still be run multiple times due to @DataProvider, with different parameters
+         * each time)
+         */
+        List<XmlSuite> suites = new ArrayList<>();
+        XmlSuite the_suite = new XmlSuite();
+        XmlTest the_test = new XmlTest(the_suite);
+        XmlClass the_class = new XmlClass(klass);
+        XmlInclude the_include = new XmlInclude(method);
+
+        the_class.getIncludedMethods().add(the_include);
+        the_test.getXmlClasses().add(the_class);
+        suites.add(the_suite);
+        testng.setXmlSuites(suites);
+
+        return testng;
+    }
+}
diff --git a/tests/core/runner/src/com/android/cts/core/runner/support/TestNgRunner.java b/tests/core/runner/src/com/android/cts/core/runner/support/TestNgRunner.java
new file mode 100644
index 0000000..eff9d3c
--- /dev/null
+++ b/tests/core/runner/src/com/android/cts/core/runner/support/TestNgRunner.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2016 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.cts.core.runner.support;
+
+import android.support.test.internal.util.AndroidRunnerParams;
+import android.util.Log;
+
+import org.junit.runner.Description;
+import org.junit.runner.Runner;
+import org.junit.runner.manipulation.Filter;
+import org.junit.runner.manipulation.Filterable;
+import org.junit.runner.manipulation.NoTestsRemainException;
+import org.junit.runner.notification.Failure;
+import org.junit.runner.notification.RunNotifier;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.HashSet;
+
+/**
+ * A {@link Runner} that can TestNG tests.
+ *
+ * <p>Implementation note: Avoid extending ParentRunner since that also has
+ * logic to handle BeforeClass/AfterClass and other junit-specific functionality
+ * that would be invalid for TestNG.</p>
+ */
+class TestNgRunner extends Runner implements Filterable {
+
+  private static final boolean DEBUG = false;
+
+  private static final String TESTNG_TEST = "org.testng.annotations.Test";
+
+  private Description mDescription;
+  /** Class name for debugging. */
+  private String mClassName;
+  /** Don't include the same method names twice. */
+  private HashSet<String> mMethodSet = new HashSet<>();
+  /** Don't actually run the test if this is true, just report passing. */
+  private final boolean mSkipExecution;
+
+  /**
+   * @param runnerParams {@link AndroidRunnerParams} that stores common runner parameters
+   */
+  TestNgRunner(Class<?> testClass, boolean skipExecution) {
+    mDescription = generateTestNgDescription(testClass);
+    mClassName = testClass.getName();
+    mSkipExecution = skipExecution;
+  }
+
+  // Runner implementation
+  @Override
+  public Description getDescription() {
+    return mDescription;
+  }
+
+  // Runner implementation
+  @Override
+  public int testCount() {
+    if (!descriptionHasChildren(getDescription())) {  // Avoid NPE when description is null.
+      return 0;
+    }
+
+    // We always follow a flat Parent->Leaf hierarchy, so no recursion necessary.
+    return getDescription().testCount();
+  }
+
+  // Filterable implementation
+  @Override
+  public void filter(Filter filter) throws NoTestsRemainException {
+    mDescription = filterDescription(mDescription, filter);
+
+    if (!descriptionHasChildren(getDescription())) {  // Avoid NPE when description is null.
+      if (DEBUG) {
+        Log.d("TestNgRunner",
+            "Filtering has removed all tests :( for class " + mClassName);
+      }
+      throw new NoTestsRemainException();
+    }
+
+    if (DEBUG) {
+      Log.d("TestNgRunner",
+          "Filtering has retained " + testCount() + " tests for class " + mClassName);
+    }
+  }
+
+  // Filterable implementation
+  @Override
+  public void run(RunNotifier notifier) {
+    if (!descriptionHasChildren(getDescription())) {  // Avoid NPE when description is null.
+      // Nothing to do.
+      return;
+    }
+
+    for (Description child : getDescription().getChildren()) {
+      String className = child.getClassName();
+      String methodName = child.getMethodName();
+
+      Class<?> klass;
+      try {
+        klass = Class.forName(className, false, Thread.currentThread().getContextClassLoader());
+      } catch (ClassNotFoundException e) {
+        throw new AssertionError(e);
+      }
+
+      notifier.fireTestStarted(child);
+
+      // CTS has a phase where it "collects" all the tests first.
+      // Just report that the test passes without actually running it here.
+      if (mSkipExecution) {
+        notifier.fireTestFinished(child);
+        continue;
+      }
+
+      // Avoid looking at all the methods by just using the string method name.
+      if (!SingleTestNgTestExecutor.execute(klass, methodName)) {
+        // TODO: get the error messages from testng somehow.
+        notifier.fireTestFailure(new Failure(child, new AssertionError()));
+      }
+      else {
+        notifier.fireTestFinished(child);
+      }
+      // TODO: Check @Test(enabled=false) and invoke #fireTestIgnored instead.
+    }
+  }
+
+  /**
+   * Recursively (preorder traversal) apply the filter to all the descriptions.
+   *
+   * @return null if the filter rejects the whole tree.
+   */
+  private static Description filterDescription(Description desc, Filter filter) {
+    if (!filter.shouldRun(desc)) {  // XX: Does the filter itself do the recursion?
+      return null;
+    }
+
+    Description newDesc = desc.childlessCopy();
+
+    // Return leafs.
+    if (!descriptionHasChildren(desc)) {
+      return newDesc;
+    }
+
+    // Filter all subtrees, only copying them if the filter accepts them.
+    for (Description child : desc.getChildren()) {
+      Description filteredChild = filterDescription(child, filter);
+
+      if (filteredChild != null) {
+        newDesc.addChild(filteredChild);
+      }
+    }
+
+    return newDesc;
+  }
+
+  private Description generateTestNgDescription(Class<?> cls) {
+    // Add the overall class description as the parent.
+    Description parent = Description.createSuiteDescription(cls);
+
+    if (DEBUG) {
+      Log.d("TestNgRunner", "Generating TestNg Description for class " + cls.getName());
+    }
+
+    // Add each test method as a child.
+    for (Method m : cls.getDeclaredMethods()) {
+
+      // Filter to only 'public void' signatures.
+      if ((m.getModifiers() & Modifier.PUBLIC) == 0) {
+        continue;
+      }
+
+      if (!m.getReturnType().equals(Void.TYPE)) {
+        continue;
+      }
+
+      // Note that TestNG methods may actually have parameters
+      // (e.g. with @DataProvider) which TestNG will populate itself.
+
+      // Add [Class, MethodName] as a Description leaf node.
+      String name = m.getName();
+
+      if (!mMethodSet.add(name)) {
+        // Overloaded methods have the same name, don't add them twice.
+        if (DEBUG) {
+          Log.d("TestNgRunner", "Already added child " + cls.getName() + "#" + name);
+        }
+        continue;
+      }
+
+      Description child = Description.createTestDescription(cls, name);
+
+      parent.addChild(child);
+
+      if (DEBUG) {
+        Log.d("TestNgRunner", "Add child " + cls.getName() + "#" + name);
+      }
+    }
+
+    return parent;
+  }
+
+  private static boolean descriptionHasChildren(Description desc) {
+    // Note: Although "desc.isTest()" is equivalent to "!desc.getChildren().isEmpty()"
+    // we add the pre-requisite 2 extra null checks to avoid throwing NPEs.
+    return desc != null && desc.getChildren() != null && !desc.getChildren().isEmpty();
+  }
+}
diff --git a/tests/core/runner/src/com/android/cts/core/runner/support/TestNgRunnerBuilder.java b/tests/core/runner/src/com/android/cts/core/runner/support/TestNgRunnerBuilder.java
new file mode 100644
index 0000000..621eeb5
--- /dev/null
+++ b/tests/core/runner/src/com/android/cts/core/runner/support/TestNgRunnerBuilder.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2016 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.cts.core.runner.support;
+
+import android.support.test.internal.util.AndroidRunnerParams;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+
+import org.junit.runner.Runner;
+import org.junit.runners.model.RunnerBuilder;
+
+import org.testng.annotations.Test;
+
+/**
+ * A {@link RunnerBuilder} that can TestNG tests.
+ */
+class TestNgRunnerBuilder extends RunnerBuilder {
+  private static final String TESTNG_TEST = "org.testng.annotations.Test";
+  private final AndroidRunnerParams mRunnerParams;
+
+  /**
+   * @param runnerParams {@link AndroidRunnerParams} that stores common runner parameters
+   */
+  TestNgRunnerBuilder(AndroidRunnerParams runnerParams) {
+    mRunnerParams = runnerParams;
+  }
+
+
+  // Returns a TestNG runner for this class, only if it is a class
+  // annotated with testng's @Test or has any methods with @Test in it.
+  @Override
+  public Runner runnerForClass(Class<?> testClass) {
+    if (isTestNgTestClass(testClass)) {
+      return new TestNgRunner(testClass, mRunnerParams.isSkipExecution());
+    }
+
+    return null;
+  }
+
+  private static boolean isTestNgTestClass(Class<?> cls) {
+    // TestNG test is either marked @Test at the class
+    if (cls.getAnnotation(Test.class) != null) {
+      return true;
+    }
+
+    // Or It's marked @Test at the method level
+    for (Method m : cls.getDeclaredMethods()) {
+      if (m.getAnnotation(Test.class) != null) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+}
diff --git a/tests/dram/src/android/dram/cts/BandwidthTest.java b/tests/dram/src/android/dram/cts/BandwidthTest.java
index 2096402..1eb307b 100644
--- a/tests/dram/src/android/dram/cts/BandwidthTest.java
+++ b/tests/dram/src/android/dram/cts/BandwidthTest.java
@@ -35,6 +35,7 @@
  */
 public class BandwidthTest extends CtsAndroidTestCase {
     private static final String TAG = BandwidthTest.class.getSimpleName();
+    private static final String REPORT_LOG_NAME = "CtsDramTestCases";
     private static final int MEMCPY_REPETITION = 10;
     private static final int MEMSET_REPETITION = 30;
     private static final int REPEAT_IN_EACH_CALL = 100;
@@ -165,13 +166,13 @@
         for (int i = 0; i < MEMCPY_REPETITION; i++) {
             result[i] = MemoryNative.runMemcpy(bufferSize, repeatInEachCall);
         }
-        DeviceReportLog report = new DeviceReportLog();
-        report.addValues("memcpy time", result, ResultType.LOWER_BETTER,
-                ResultUnit.MS);
+        String streamName = "run_memcpy";
+        DeviceReportLog report = new DeviceReportLog(REPORT_LOG_NAME, streamName);
+        report.addValue("buffer_size", bufferSize, ResultType.NEUTRAL, ResultUnit.NONE);
+        report.addValues("memcpy_time", result, ResultType.LOWER_BETTER, ResultUnit.MS);
         double[] mbps = Stat.calcRatePerSecArray(
                 (double)bufferSize * repeatInEachCall / 1024.0 / 1024.0, result);
-        report.addValues("memcpy throughput", mbps, ResultType.HIGHER_BETTER,
-                ResultUnit.MBPS);
+        report.addValues("memcpy_throughput", mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
         Stat.StatResult stat = Stat.getStatWithOutlierRejection(mbps, OUTLIER_THRESHOLD);
         if (stat.mDataCount != result.length) {
             Log.w(TAG, "rejecting " + (result.length - stat.mDataCount) + " outliers");
@@ -183,9 +184,9 @@
         double pixels = size.x * size.y;
         // now this represents how many times the whole screen can be copied in a sec.
         double screensPerSecAverage = stat.mAverage / pixels * 1024.0 * 1024.0 / 4.0;
-        report.addValue("memcpy in fps", screensPerSecAverage,
-                ResultType.HIGHER_BETTER, ResultUnit.FPS);
-        report.setSummary("memcpy throughput", stat.mAverage, ResultType.HIGHER_BETTER,
+        report.addValue("memcpy_in_fps", screensPerSecAverage, ResultType.HIGHER_BETTER,
+                ResultUnit.FPS);
+        report.setSummary("memcpy_throughput_average", stat.mAverage, ResultType.HIGHER_BETTER,
                 ResultUnit.MBPS);
         report.submit(getInstrumentation());
     }
@@ -200,11 +201,13 @@
         for (int i = 0; i < MEMSET_REPETITION; i++) {
             result[i] = MemoryNative.runMemset(bufferSize, repeatInEachCall, MEMSET_CHAR);
         }
-        DeviceReportLog report = new DeviceReportLog();
-        report.addValues("memset time", result, ResultType.LOWER_BETTER, ResultUnit.MS);
+        String streamName = "run_memset";
+        DeviceReportLog report = new DeviceReportLog(REPORT_LOG_NAME, streamName);
+        report.addValue("buffer_size", bufferSize, ResultType.NEUTRAL, ResultUnit.NONE);
+        report.addValues("memset_time", result, ResultType.LOWER_BETTER, ResultUnit.MS);
         double[] mbps = Stat.calcRatePerSecArray(
                 (double)bufferSize * repeatInEachCall / 1024.0 / 1024.0, result);
-        report.addValues("memset throughput", mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
+        report.addValues("memset_throughput", mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
         Stat.StatResult stat = Stat.getStatWithOutlierRejection(mbps, OUTLIER_THRESHOLD);
         if (stat.mDataCount != result.length) {
             Log.w(TAG, "rejecting " + (result.length - stat.mDataCount) + " outliers");
@@ -216,9 +219,9 @@
         double pixels = size.x * size.y;
         // now this represents how many times the whole screen can be copied in a sec.
         double screensPerSecAverage = stat.mAverage / pixels * 1024.0 * 1024.0 / 4.0;
-        report.addValue("memset in fps", screensPerSecAverage,
-                ResultType.HIGHER_BETTER, ResultUnit.FPS);
-        report.setSummary("memset throughput", stat.mAverage, ResultType.HIGHER_BETTER,
+        report.addValue("memset_in_fps", screensPerSecAverage, ResultType.HIGHER_BETTER,
+                ResultUnit.FPS);
+        report.setSummary("memset_throughput_average", stat.mAverage, ResultType.HIGHER_BETTER,
                 ResultUnit.MBPS);
         report.submit(getInstrumentation());
     }
diff --git a/tests/filesystem/src/android/filesystem/cts/AlmostFullTest.java b/tests/filesystem/src/android/filesystem/cts/AlmostFullTest.java
index 991f9bf..a6f069d 100644
--- a/tests/filesystem/src/android/filesystem/cts/AlmostFullTest.java
+++ b/tests/filesystem/src/android/filesystem/cts/AlmostFullTest.java
@@ -34,6 +34,7 @@
     private static final String DIR_RANDOM_WR = "RANDOM_WR";
     private static final String DIR_RANDOM_RD = "RANDOM_RD";
     private static final String TAG = "AlmostFullTest";
+    private static final String REPORT_LOG_NAME = "CtsFileSystemTestCases";
 
     private static final long FREE_SPACE_FINAL = 1000L * 1024 * 1024L;
 
@@ -107,10 +108,9 @@
         }
         final int BUFFER_SIZE = 10 * 1024 * 1024;
         final int NUMBER_REPETITION = 10;
-        DeviceReportLog report = new DeviceReportLog();
-        FileUtil.doSequentialUpdateTest(getContext(), DIR_SEQ_UPDATE, report, FILE_SIZE,
-                BUFFER_SIZE, NUMBER_REPETITION);
-        report.submit(getInstrumentation());
+        String streamName = "test_sequential_update";
+        FileUtil.doSequentialUpdateTest(getContext(), DIR_SEQ_UPDATE, FILE_SIZE, BUFFER_SIZE,
+                NUMBER_REPETITION, REPORT_LOG_NAME, streamName);
     }
 
     // TODO: file size too small and caching will give wrong better result.
@@ -124,7 +124,8 @@
             Log.w(TAG, "too little space: " + freeDisk);
             return;
         }
-        DeviceReportLog report = new DeviceReportLog();
+        String streamName = "test_random_read";
+        DeviceReportLog report = new DeviceReportLog(REPORT_LOG_NAME, streamName);
         FileUtil.doRandomReadTest(getContext(), DIR_RANDOM_RD, report, fileSize, BUFFER_SIZE);
         report.submit(getInstrumentation());
     }
@@ -138,7 +139,8 @@
             Log.w(TAG, "too little space: " + freeDisk);
             return;
         }
-        DeviceReportLog report = new DeviceReportLog();
+        String streamName = "test_random_update";
+        DeviceReportLog report = new DeviceReportLog(REPORT_LOG_NAME, streamName);
         FileUtil.doRandomWriteTest(getContext(), DIR_RANDOM_WR, report, fileSize, BUFFER_SIZE);
         report.submit(getInstrumentation());
     }
diff --git a/tests/filesystem/src/android/filesystem/cts/FileUtil.java b/tests/filesystem/src/android/filesystem/cts/FileUtil.java
index baefaa0..fc8eee0 100755
--- a/tests/filesystem/src/android/filesystem/cts/FileUtil.java
+++ b/tests/filesystem/src/android/filesystem/cts/FileUtil.java
@@ -20,6 +20,7 @@
 import android.cts.util.SystemUtil;
 import android.util.Log;
 
+import com.android.compatibility.common.util.DeviceReportLog;
 import com.android.compatibility.common.util.MeasureRun;
 import com.android.compatibility.common.util.MeasureTime;
 import com.android.compatibility.common.util.ReportLog;
@@ -303,13 +304,12 @@
         randomFile.close();
         double[] mbps = Stat.calcRatePerSecArray((double)fileSize / runsInOneGo / 1024 / 1024,
                 times);
-        report.addValues("read throughput",
-                mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
+        report.addValues("read_throughput", mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
         // This is just the amount of IO returned from kernel. So this is performance neutral.
-        report.addValues("read amount", rdAmount, ResultType.NEUTRAL, ResultUnit.BYTE);
+        report.addValues("read_amount", rdAmount, ResultType.NEUTRAL, ResultUnit.BYTE);
         Stat.StatResult stat = Stat.getStat(mbps);
 
-        report.setSummary("read throughput", stat.mAverage, ResultType.HIGHER_BETTER,
+        report.setSummary("read_throughput_average", stat.mAverage, ResultType.HIGHER_BETTER,
                 ResultUnit.MBPS);
     }
 
@@ -356,13 +356,11 @@
         randomFile.close();
         double[] mbps = Stat.calcRatePerSecArray((double)fileSize / runsInOneGo / 1024 / 1024,
                 times);
-        report.addValues("write throughput",
-                mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
-        report.addValues("write amount", wrAmount, ResultType.NEUTRAL,
-                ResultUnit.BYTE);
+        report.addValues("write_throughput", mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
+        report.addValues("write_amount", wrAmount, ResultType.NEUTRAL, ResultUnit.BYTE);
         Stat.StatResult stat = Stat.getStat(mbps);
 
-        report.setSummary("write throughput", stat.mAverage, ResultType.HIGHER_BETTER,
+        report.setSummary("write_throughput_average", stat.mAverage, ResultType.HIGHER_BETTER,
                 ResultUnit.MBPS);
     }
 
@@ -376,8 +374,9 @@
      * @param numberRepetition
      * @throws IOException
      */
-    public static void doSequentialUpdateTest(Context context, String dirName, ReportLog report,
-            long fileSize, int bufferSize, int numberRepetition) throws Exception {
+    public static void doSequentialUpdateTest(Context context, String dirName, long fileSize,
+            int bufferSize, int numberRepetition, String reportName, String streamName)
+            throws Exception {
         File file = FileUtil.createNewFilledFile(context,
                 dirName, fileSize);
         final byte[] data = FileUtil.generateRandomData(bufferSize);
@@ -385,6 +384,8 @@
         double[] mbpsAll = new double[numberRepetition * numberRepeatInOneRun];
         for (int i = 0; i < numberRepetition; i++) {
             Log.i(TAG, "starting " + i + " -th round");
+            DeviceReportLog report = new DeviceReportLog(reportName, streamName);
+            report.addValue("round", i,  ResultType.NEUTRAL, ResultUnit.NONE);
             final RandomAccessFile randomFile = new RandomAccessFile(file, "rwd");  // force O_SYNC
             randomFile.seek(0L);
             double[] times = MeasureTime.measure(numberRepeatInOneRun, new MeasureRun() {
@@ -397,15 +398,18 @@
             randomFile.close();
             double[] mbps = Stat.calcRatePerSecArray((double)bufferSize / 1024 / 1024,
                     times);
-            report.addValues(i + "-th round throughput",
-                    mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
+            report.addValues("throughput", mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
             int offset = i * numberRepeatInOneRun;
             for (int j = 0; j < mbps.length; j++) {
                 mbpsAll[offset + j] = mbps[j];
             }
+            report.submit();
         }
         Stat.StatResult stat = Stat.getStat(mbpsAll);
-        report.setSummary("update throughput", stat.mAverage, ResultType.HIGHER_BETTER,
+        DeviceReportLog report = new DeviceReportLog(reportName, String.format("%s_average",
+                streamName));
+        report.addValue("update_throughput", stat.mAverage, ResultType.HIGHER_BETTER,
                 ResultUnit.MBPS);
+        report.submit();
     }
 }
diff --git a/tests/filesystem/src/android/filesystem/cts/RandomRWTest.java b/tests/filesystem/src/android/filesystem/cts/RandomRWTest.java
index dff3723..d2d4e84 100644
--- a/tests/filesystem/src/android/filesystem/cts/RandomRWTest.java
+++ b/tests/filesystem/src/android/filesystem/cts/RandomRWTest.java
@@ -24,6 +24,7 @@
 public class RandomRWTest extends CtsAndroidTestCase {
     private static final String DIR_RANDOM_WR = "RANDOM_WR";
     private static final String DIR_RANDOM_RD = "RANDOM_RD";
+    private static final String REPORT_LOG_NAME = "CtsFileSystemTestCases";
 
     @Override
     protected void tearDown() throws Exception {
@@ -39,7 +40,8 @@
         if (fileSize == 0) { // not enough space, give up
             return;
         }
-        DeviceReportLog report = new DeviceReportLog();
+        String streamName = "test_random_read";
+        DeviceReportLog report = new DeviceReportLog(REPORT_LOG_NAME, streamName);
         FileUtil.doRandomReadTest(getContext(), DIR_RANDOM_RD, report, fileSize,
                 READ_BUFFER_SIZE);
         report.submit(getInstrumentation());
@@ -50,7 +52,8 @@
     public void testRandomUpdate() throws Exception {
         final int WRITE_BUFFER_SIZE = 4 * 1024;
         final long fileSize = 256 * 1024 * 1024;
-        DeviceReportLog report = new DeviceReportLog();
+        String streamName = "test_random_update";
+        DeviceReportLog report = new DeviceReportLog(REPORT_LOG_NAME, streamName);
         FileUtil.doRandomWriteTest(getContext(), DIR_RANDOM_WR, report, fileSize,
                 WRITE_BUFFER_SIZE);
         report.submit(getInstrumentation());
diff --git a/tests/filesystem/src/android/filesystem/cts/SequentialRWTest.java b/tests/filesystem/src/android/filesystem/cts/SequentialRWTest.java
index 3b7a45f..d725614 100644
--- a/tests/filesystem/src/android/filesystem/cts/SequentialRWTest.java
+++ b/tests/filesystem/src/android/filesystem/cts/SequentialRWTest.java
@@ -34,6 +34,7 @@
     private static final String DIR_SEQ_WR = "SEQ_WR";
     private static final String DIR_SEQ_UPDATE = "SEQ_UPDATE";
     private static final String DIR_SEQ_RD = "SEQ_RD";
+    private static final String REPORT_LOG_NAME = "CtsFileSystemTestCases";
     private static final int BUFFER_SIZE = 10 * 1024 * 1024;
 
     @Override
@@ -51,7 +52,8 @@
             return;
         }
         final int numberOfFiles =(int)(fileSize / BUFFER_SIZE);
-        DeviceReportLog report = new DeviceReportLog();
+        String streamName = "test_single_sequential_write";
+        DeviceReportLog report = new DeviceReportLog(REPORT_LOG_NAME, streamName);
         report.addValue("files", numberOfFiles, ResultType.NEUTRAL, ResultUnit.COUNT);
         final byte[] data = FileUtil.generateRandomData(BUFFER_SIZE);
         final File[] files = FileUtil.createNewFiles(getContext(), DIR_SEQ_WR,
@@ -66,12 +68,10 @@
             }
         });
         double[] mbps = Stat.calcRatePerSecArray((double)BUFFER_SIZE / 1024 / 1024, times);
-        report.addValues("write throughput",
-                mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
-        report.addValues("write amount", wrAmount, ResultType.NEUTRAL,
-                ResultUnit.BYTE);
+        report.addValues("write_throughput", mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
+        report.addValues("write_amount", wrAmount, ResultType.NEUTRAL, ResultUnit.BYTE);
         Stat.StatResult stat = Stat.getStat(mbps);
-        report.setSummary("write throughput", stat.mAverage, ResultType.HIGHER_BETTER,
+        report.setSummary("write_throughput_average", stat.mAverage, ResultType.HIGHER_BETTER,
                 ResultUnit.MBPS);
         report.submit(getInstrumentation());
     }
@@ -83,10 +83,9 @@
             return;
         }
         final int NUMBER_REPETITION = 6;
-        DeviceReportLog report = new DeviceReportLog();
-        FileUtil.doSequentialUpdateTest(getContext(), DIR_SEQ_UPDATE, report, fileSize,
-                BUFFER_SIZE, NUMBER_REPETITION);
-        report.submit(getInstrumentation());
+        String streamName = "test_single_sequential_update";
+        FileUtil.doSequentialUpdateTest(getContext(), DIR_SEQ_UPDATE, fileSize, BUFFER_SIZE,
+                NUMBER_REPETITION, REPORT_LOG_NAME, streamName);
     }
 
     @TimeoutReq(minutes = 30)
@@ -99,8 +98,10 @@
         final File file = FileUtil.createNewFilledFile(getContext(),
                 DIR_SEQ_RD, fileSize);
         long finish = System.currentTimeMillis();
-        DeviceReportLog report = new DeviceReportLog();
-        report.addValue("write throughput for test file of length " + fileSize,
+        String streamName = "test_single_sequential_read";
+        DeviceReportLog report = new DeviceReportLog(REPORT_LOG_NAME, streamName);
+        report.addValue("file_size", fileSize, ResultType.NEUTRAL, ResultUnit.NONE);
+        report.addValue("write_throughput",
                 Stat.calcRatePerSec((double)fileSize / 1024 / 1024, finish - start),
                 ResultType.HIGHER_BETTER, ResultUnit.MBPS);
 
@@ -121,10 +122,9 @@
             }
         });
         double[] mbps = Stat.calcRatePerSecArray((double)fileSize / 1024 / 1024, times);
-        report.addValues("read throughput",
-                mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
+        report.addValues("read_throughput", mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
         Stat.StatResult stat = Stat.getStat(mbps);
-        report.setSummary("read throughput", stat.mAverage, ResultType.HIGHER_BETTER,
+        report.setSummary("read_throughput_average", stat.mAverage, ResultType.HIGHER_BETTER,
                 ResultUnit.MBPS);
         report.submit(getInstrumentation());
     }
diff --git a/tests/jank/src/android/jank/cts/CtsJankTestBase.java b/tests/jank/src/android/jank/cts/CtsJankTestBase.java
index fb2f867..6015726 100644
--- a/tests/jank/src/android/jank/cts/CtsJankTestBase.java
+++ b/tests/jank/src/android/jank/cts/CtsJankTestBase.java
@@ -27,22 +27,23 @@
 
 public abstract class CtsJankTestBase extends JankTestBase {
 
+    private static final String REPORT_LOG_NAME = "CtsJankDeviceTestCases";
     private UiDevice mDevice;
     private DeviceReportLog mLog;
 
     @Override
     public void afterTest(Bundle metrics) {
         String source = String.format("%s#%s", getClass().getCanonicalName(), getName());
-        mLog.addValue(source, WindowContentFrameStatsMonitor.KEY_AVG_FPS,
+        mLog.addValue(source, "frame_fps",
                 metrics.getDouble(WindowContentFrameStatsMonitor.KEY_AVG_FPS),
                 ResultType.HIGHER_BETTER, ResultUnit.FPS);
-        mLog.addValue(source, WindowContentFrameStatsMonitor.KEY_AVG_LONGEST_FRAME,
+        mLog.addValue(source, "frame_max_frame_duration",
                 metrics.getDouble(WindowContentFrameStatsMonitor.KEY_AVG_LONGEST_FRAME),
                 ResultType.LOWER_BETTER, ResultUnit.MS);
-        mLog.addValue(source, WindowContentFrameStatsMonitor.KEY_MAX_NUM_JANKY,
+        mLog.addValue(source, "frame_max_jank",
                 metrics.getInt(WindowContentFrameStatsMonitor.KEY_MAX_NUM_JANKY),
                 ResultType.LOWER_BETTER, ResultUnit.COUNT);
-        mLog.setSummary(WindowContentFrameStatsMonitor.KEY_AVG_NUM_JANKY,
+        mLog.setSummary("frame_avg_jank",
                 metrics.getDouble(WindowContentFrameStatsMonitor.KEY_AVG_NUM_JANKY),
                 ResultType.LOWER_BETTER, ResultUnit.COUNT);
     }
@@ -50,7 +51,8 @@
     @Override
     protected void setUp() throws Exception {
         super.setUp();
-        mLog = new DeviceReportLog();
+        String streamName = "cts_device_jank_test";
+        mLog = new DeviceReportLog(REPORT_LOG_NAME, streamName);
         // fix device orientation
         mDevice = UiDevice.getInstance(getInstrumentation());
         mDevice.setOrientationNatural();
diff --git a/tests/jdwp/runner/host-side/src/com/android/compatibility/testtype/DalvikTest.java b/tests/jdwp/runner/host-side/src/com/android/compatibility/testtype/DalvikTest.java
index 4daabb7..d8e181c 100644
--- a/tests/jdwp/runner/host-side/src/com/android/compatibility/testtype/DalvikTest.java
+++ b/tests/jdwp/runner/host-side/src/com/android/compatibility/testtype/DalvikTest.java
@@ -37,6 +37,7 @@
 import com.android.tradefed.testtype.IRuntimeHintProvider;
 import com.android.tradefed.testtype.IShardableTest;
 import com.android.tradefed.testtype.ITestCollector;
+import com.android.tradefed.testtype.ITestFileFilterReceiver;
 import com.android.tradefed.testtype.ITestFilterReceiver;
 import com.android.tradefed.util.ArrayUtil;
 import com.android.tradefed.util.TimeVal;
@@ -45,8 +46,10 @@
 import vogar.ExpectationStore;
 import vogar.ModeId;
 
+import java.io.BufferedReader;
 import java.io.File;
 import java.io.FilenameFilter;
+import java.io.FileReader;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -54,7 +57,6 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
-import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
@@ -63,7 +65,8 @@
  * A wrapper to run tests against Dalvik.
  */
 public class DalvikTest implements IAbiReceiver, IBuildReceiver, IDeviceTest, IRemoteTest,
-        IRuntimeHintProvider, IShardableTest, ITestCollector, ITestFilterReceiver {
+        IRuntimeHintProvider, IShardableTest, ITestCollector, ITestFileFilterReceiver,
+        ITestFilterReceiver {
 
     private static final String TAG = DalvikTest.class.getSimpleName();
 
@@ -131,6 +134,16 @@
             description = "The exclude filters of the test name to run.")
     private List<String> mExcludeFilters = new ArrayList<>();
 
+    @Option(name = "test-file-include-filter",
+            description="A file containing a list of line separated test classes and optionally"
+            + " methods to include")
+    private File mIncludeTestFile = null;
+
+    @Option(name = "test-file-exclude-filter",
+            description="A file containing a list of line separated test classes and optionally"
+            + " methods to exclude")
+    private File mExcludeTestFile = null;
+
     @Option(name = "runtime-hint",
             isTimeVal = true,
             description="The hint about the test's runtime.")
@@ -224,6 +237,22 @@
      * {@inheritDoc}
      */
     @Override
+    public void setIncludeTestFile(File testFile) {
+        mIncludeTestFile = testFile;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setExcludeTestFile(File testFile) {
+        mExcludeTestFile = testFile;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
     public long getRuntimeHint() {
         return mRuntimeHint;
     }
@@ -244,56 +273,33 @@
         String abiName = mAbi.getName();
         String bitness = AbiUtils.getBitness(abiName);
 
-        File temp = null;
-        PrintWriter out = null;
+        File tmpExcludeFile = null;
         try {
-            Set<File> expectationFiles = new HashSet<>();
-            for (File f : mBuildHelper.getTestsDir().listFiles(
-                    new ExpectationFileFilter(mRunName))) {
-                expectationFiles.add(f);
-            }
-            ExpectationStore store = ExpectationStore.parse(expectationFiles, ModeId.DEVICE);
-
-            ExpectationStore resourceStore = null;
-            if (mKnownFailures != null) {
-                Splitter splitter = Splitter.on(',').trimResults();
-                Set<String> knownFailuresFileList =
-                        new LinkedHashSet<>(splitter.splitToList(mKnownFailures));
-                resourceStore = ExpectationStore.parseResources(
-                        getClass(), knownFailuresFileList, ModeId.DEVICE);
-            }
-
-            // Work around because there are to many expectations to pass via command line
-            temp = File.createTempFile("excludes", "txt");
-            out = new PrintWriter(temp);
-            for (String exclude : store.getAllFailures().keySet()) {
-                out.println(exclude);
-            }
-            for (String exclude : store.getAllOutComes().keySet()) {
-                out.println(exclude);
-            }
-            // Add expectations from resources
-            if (resourceStore != null) {
-                for (String exclude : resourceStore.getAllFailures().keySet()) {
-                    out.println(exclude);
-                }
-                for (String exclude : resourceStore.getAllOutComes().keySet()) {
-                    out.println(exclude);
-                }
-            }
-            out.flush();
-            if (!mDevice.pushFile(temp, EXCLUDE_FILE)) {
-                Log.logAndDisplay(LogLevel.ERROR, TAG, "Couldn't push file: " + temp);
+            // push one file of exclude filters to the device
+            tmpExcludeFile = getExcludeFile();
+            if (!mDevice.pushFile(tmpExcludeFile, EXCLUDE_FILE)) {
+                Log.logAndDisplay(LogLevel.ERROR, TAG, "Couldn't push file: " + tmpExcludeFile);
             }
         } catch (IOException e) {
-            throw new RuntimeException("Failed to parse expectations");
+            throw new RuntimeException("Failed to parse expectations", e);
         } finally {
-            if (out != null) {
-                out.close();
+            if (tmpExcludeFile != null) {
+                tmpExcludeFile.delete();
             }
-            temp.delete();
         }
 
+        // push one file of include filters to the device, if file exists
+        if (mIncludeTestFile != null) {
+            String path = mIncludeTestFile.getAbsolutePath();
+            if (!mIncludeTestFile.isFile() || !mIncludeTestFile.canRead()) {
+                throw new RuntimeException(String.format("Failed to read include file %s", path));
+            }
+            if (!mDevice.pushFile(mIncludeTestFile, INCLUDE_FILE)) {
+                Log.logAndDisplay(LogLevel.ERROR, TAG, "Couldn't push file: " + path);
+            }
+        }
+
+
         // Create command
         mDalvikArgs.add("-Duser.name=shell");
         mDalvikArgs.add("-Duser.language=en");
@@ -374,6 +380,82 @@
         mDevice.executeShellCommand(command, receiver, mPerTestTimeout, TimeUnit.MINUTES, 1);
     }
 
+    /*
+     * Due to known failures, there are typically too many excludes to pass via command line.
+     * Collect excludes from .expectation files in the testcases directory, from files in the
+     * module's resources directory, and from mExcludeTestFile, if set.
+     */
+    private File getExcludeFile() throws IOException {
+        File excludeFile = null;
+        PrintWriter out = null;
+
+        try {
+            excludeFile = File.createTempFile("excludes", "txt");
+            out = new PrintWriter(excludeFile);
+            // create expectation store from set of expectation files found in testcases dir
+            Set<File> expectationFiles = new HashSet<>();
+            for (File f : mBuildHelper.getTestsDir().listFiles(
+                    new ExpectationFileFilter(mRunName))) {
+                expectationFiles.add(f);
+            }
+            ExpectationStore testsDirStore =
+                    ExpectationStore.parse(expectationFiles, ModeId.DEVICE);
+            // create expectation store from expectation files found in module resources dir
+            ExpectationStore resourceStore = null;
+            if (mKnownFailures != null) {
+                Splitter splitter = Splitter.on(',').trimResults();
+                Set<String> knownFailuresFiles =
+                        new HashSet<>(splitter.splitToList(mKnownFailures));
+                resourceStore = ExpectationStore.parseResources(
+                        getClass(), knownFailuresFiles, ModeId.DEVICE);
+            }
+            // Add expectations from testcases dir
+            for (String exclude : testsDirStore.getAllFailures().keySet()) {
+                out.println(exclude);
+            }
+            for (String exclude : testsDirStore.getAllOutComes().keySet()) {
+                out.println(exclude);
+            }
+            // Add expectations from resources dir
+            if (resourceStore != null) {
+                for (String exclude : resourceStore.getAllFailures().keySet()) {
+                    out.println(exclude);
+                }
+                for (String exclude : resourceStore.getAllOutComes().keySet()) {
+                    out.println(exclude);
+                }
+            }
+            // Add excludes from test-file-exclude-filter option
+            for (String exclude : getFiltersFromFile(mExcludeTestFile)) {
+                out.println(exclude);
+            }
+            out.flush();
+        } finally {
+            if (out != null) {
+                out.close();
+            }
+        }
+        return excludeFile;
+    }
+
+
+    /*
+     * Helper method that reads filters from a file into a set.
+     * Returns an empty set given a null file
+     */
+    private static Set<String> getFiltersFromFile(File f) throws IOException {
+        Set<String> filters = new HashSet<String>();
+        if (f != null) {
+            BufferedReader reader = new BufferedReader(new FileReader(f));
+            String filter = null;
+            while ((filter = reader.readLine()) != null) {
+                filters.add(filter);
+            }
+            reader.close();
+        }
+        return filters;
+    }
+
     /**
      * {@inheritDoc}
      */
diff --git a/tests/libcore/Android.mk b/tests/libcore/Android.mk
index b24babd..72b6ebc 100644
--- a/tests/libcore/Android.mk
+++ b/tests/libcore/Android.mk
@@ -22,6 +22,7 @@
     apache-harmony-tests \
     bouncycastle-nojarjar \
     conscrypt-tests \
+    core-ojtests-public \
     core-tests \
     cts-core-test-runner \
     jsr166-tests \
diff --git a/tests/libcore/AndroidTest.xml b/tests/libcore/AndroidTest.xml
index b2fc10a..1e47502 100644
--- a/tests/libcore/AndroidTest.xml
+++ b/tests/libcore/AndroidTest.xml
@@ -32,5 +32,7 @@
         <option name="instrumentation-arg" key="core-expectations"
                 value="/knownfailures.txt,/brokentests.txt,/icebox.txt,/taggedtests.txt,/expectations/cts-runner-specific-failures.txt" />
         <option name="runtime-hint" value="45m"/>
+        <!-- 20x default timeout of 600sec -->
+        <option name="shell-timeout" value="12000000"/>
     </test>
 </configuration>
diff --git a/tests/openglperf2/src/android/opengl2/cts/primitive/GLPrimitiveBenchmark.java b/tests/openglperf2/src/android/opengl2/cts/primitive/GLPrimitiveBenchmark.java
index d737dbf..cbd8d52 100644
--- a/tests/openglperf2/src/android/opengl2/cts/primitive/GLPrimitiveBenchmark.java
+++ b/tests/openglperf2/src/android/opengl2/cts/primitive/GLPrimitiveBenchmark.java
@@ -30,6 +30,7 @@
     private static final int NUM_FRAMES = 100;
     private static final int NUM_ITERATIONS = 8;
     private static final int TIMEOUT = 1000000;
+    private static final String REPORT_LOG_NAME = "CtsOpenGlPerf2TestCases";
 
     public GLPrimitiveBenchmark() {
         super(GLPrimitiveActivity.class);
@@ -40,7 +41,9 @@
      */
     @TimeoutReq(minutes = 100)
     public void testFullPipelineOffscreen() throws Exception {
-        runBenchmark(BenchmarkName.FullPipeline, true, NUM_FRAMES, NUM_ITERATIONS, TIMEOUT);
+        String streamName = "test_full_pipeline_offscreen";
+        runBenchmark(BenchmarkName.FullPipeline, true, NUM_FRAMES, NUM_ITERATIONS, TIMEOUT,
+                streamName);
     }
 
     /**
@@ -48,7 +51,9 @@
      */
     @TimeoutReq(minutes = 100)
     public void testFullPipelineOnscreen() throws Exception {
-        runBenchmark(BenchmarkName.FullPipeline, false, NUM_FRAMES, NUM_ITERATIONS, TIMEOUT);
+        String streamName = "test_full_pipeline_onscreen";
+        runBenchmark(BenchmarkName.FullPipeline, false, NUM_FRAMES, NUM_ITERATIONS, TIMEOUT,
+                streamName);
     }
 
     /**
@@ -56,7 +61,9 @@
      */
     @TimeoutReq(minutes = 100)
     public void testPixelOutputOffscreen() throws Exception {
-        runBenchmark(BenchmarkName.PixelOutput, true, NUM_FRAMES, NUM_ITERATIONS, TIMEOUT);
+        String streamName = "test_pixel_output_offscreen";
+        runBenchmark(BenchmarkName.PixelOutput, true, NUM_FRAMES, NUM_ITERATIONS, TIMEOUT,
+                streamName);
     }
 
     /**
@@ -64,7 +71,9 @@
      */
     @TimeoutReq(minutes = 100)
     public void testPixelOutputOnscreen() throws Exception {
-        runBenchmark(BenchmarkName.PixelOutput, false, NUM_FRAMES, NUM_ITERATIONS, TIMEOUT);
+        String streamName = "test_pixel_output_onscreen";
+        runBenchmark(BenchmarkName.PixelOutput, false, NUM_FRAMES, NUM_ITERATIONS, TIMEOUT,
+                streamName);
     }
 
     /**
@@ -72,7 +81,9 @@
      */
     @TimeoutReq(minutes = 100)
     public void testShaderPerfOffscreen() throws Exception {
-        runBenchmark(BenchmarkName.ShaderPerf, true, NUM_FRAMES, NUM_ITERATIONS, TIMEOUT);
+        String streamName = "test_shader_perf_offscreen";
+        runBenchmark(BenchmarkName.ShaderPerf, true, NUM_FRAMES, NUM_ITERATIONS, TIMEOUT,
+                streamName);
     }
 
     /**
@@ -80,7 +91,9 @@
      */
     @TimeoutReq(minutes = 100)
     public void testShaderPerfOnscreen() throws Exception {
-        runBenchmark(BenchmarkName.ShaderPerf, false, NUM_FRAMES, NUM_ITERATIONS, TIMEOUT);
+        String streamName = "test_shader_perf_onscreen";
+        runBenchmark(BenchmarkName.ShaderPerf, false, NUM_FRAMES, NUM_ITERATIONS, TIMEOUT,
+                streamName);
     }
 
     /**
@@ -88,7 +101,9 @@
      */
     @TimeoutReq(minutes = 100)
     public void testContextSwitchOffscreen() throws Exception {
-        runBenchmark(BenchmarkName.ContextSwitch, true, NUM_FRAMES, NUM_ITERATIONS, TIMEOUT);
+        String streamName = "test_context_switch_offscreen";
+        runBenchmark(BenchmarkName.ContextSwitch, true, NUM_FRAMES, NUM_ITERATIONS, TIMEOUT,
+                streamName);
     }
 
     /**
@@ -96,7 +111,9 @@
      */
     @TimeoutReq(minutes = 100)
     public void testContextSwitchOnscreen() throws Exception {
-        runBenchmark(BenchmarkName.ContextSwitch, false, NUM_FRAMES, NUM_ITERATIONS, TIMEOUT);
+        String streamName = "test_context_switch_onscreen";
+        runBenchmark(BenchmarkName.ContextSwitch, false, NUM_FRAMES, NUM_ITERATIONS, TIMEOUT,
+                streamName);
     }
 
     /**
@@ -107,10 +124,11 @@
      * @param numFrames The number of frames to render.
      * @param numIterations The number of iterations to run, each iteration has a bigger workload.
      * @param timeout The milliseconds to wait for an iteration of the benchmark before timing out.
+     * @param streamName The name of the stream of test metrics.
      * @throws Exception If the benchmark could not be run.
      */
     private void runBenchmark(BenchmarkName benchmark, boolean offscreen, int numFrames,
-            int numIterations, int timeout) throws Exception {
+            int numIterations, int timeout, String streamName) throws Exception {
         String benchmarkName = benchmark.toString();
         Intent intent = new Intent();
         intent.putExtra(GLActivityIntentKeys.INTENT_EXTRA_BENCHMARK_NAME, benchmarkName);
@@ -132,8 +150,8 @@
 
             // TODO: maybe standard deviation / RMSE will be useful?
 
-            DeviceReportLog report = new DeviceReportLog();
-            report.setSummary("Average FPS", score, ResultType.HIGHER_BETTER, ResultUnit.SCORE);
+            DeviceReportLog report = new DeviceReportLog(REPORT_LOG_NAME, streamName);
+            report.setSummary("average_fps", score, ResultType.HIGHER_BETTER, ResultUnit.SCORE);
             report.submit(getInstrumentation());
         }
     }
diff --git a/tests/openglperf2/src/android/opengl2/cts/reference/GLReferenceBenchmark.java b/tests/openglperf2/src/android/opengl2/cts/reference/GLReferenceBenchmark.java
index 196207c..898ccfd 100644
--- a/tests/openglperf2/src/android/opengl2/cts/reference/GLReferenceBenchmark.java
+++ b/tests/openglperf2/src/android/opengl2/cts/reference/GLReferenceBenchmark.java
@@ -31,6 +31,7 @@
     private static final int NUM_SCENES = 2;
     private static final int NUM_FRAMES = NUM_FRAMES_PER_SCENE * NUM_SCENES;
     private static final int TIMEOUT = 1000000;
+    private static final String REPORT_LOG_NAME = "CtsOpenGlPerf2TestCases";
 
     public GLReferenceBenchmark() {
         super(GLReferenceActivity.class);
@@ -65,15 +66,16 @@
             double updateAverage = updateSum / NUM_FRAMES;
             double renderAverage = renderSum / NUM_FRAMES;
 
-            DeviceReportLog report = new DeviceReportLog();
-            report.addValues("Set Up Times", setUpTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
-            report.addValue("Update Time Average", updateAverage, ResultType.LOWER_BETTER,
+            String streamName = "test_reference_benchmark";
+            DeviceReportLog report = new DeviceReportLog(REPORT_LOG_NAME, streamName);
+            report.addValues("set_up_times", setUpTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
+            report.addValue("update_time_average", updateAverage, ResultType.LOWER_BETTER,
                     ResultUnit.MS);
-            report.addValue("Render Time Average", renderAverage, ResultType.LOWER_BETTER,
+            report.addValue("render_time_average", renderAverage, ResultType.LOWER_BETTER,
                     ResultUnit.MS);
             totalTime = setUpTimes[0] + setUpTimes[1] + setUpTimes[2] + setUpTimes[3] +
                     updateAverage + renderAverage;
-            report.setSummary("Total Time Average", totalTime, ResultType.LOWER_BETTER,
+            report.setSummary("total_time_average", totalTime, ResultType.LOWER_BETTER,
                     ResultUnit.MS);
             report.submit(getInstrumentation());
         }
diff --git a/tests/sample/src/android/sample/cts/SampleDeviceReportLogTest.java b/tests/sample/src/android/sample/cts/SampleDeviceReportLogTest.java
index 7f8e2f6..a3a6bd1 100644
--- a/tests/sample/src/android/sample/cts/SampleDeviceReportLogTest.java
+++ b/tests/sample/src/android/sample/cts/SampleDeviceReportLogTest.java
@@ -31,6 +31,12 @@
         extends ActivityInstrumentationTestCase2<SampleDeviceActivity> {
 
     /**
+     * Name of the report log. Test metrics will be written out to ths report. The name must match
+     * the test module name.
+     */
+    private static final String REPORT_LOG_NAME = "CtsSampleDeviceTestCases";
+
+    /**
      * Sample numbers used by the sample tests.
      */
     private static final int MULTIPLICATION_NUMBER_1 = 23;
@@ -60,9 +66,8 @@
         assertTrue("Multiplication result do not match", product == MULTIPLICATION_RESULT);
 
         // Log metrics from the test.
-        String reportLogName = "SampleDeviceTestMetrics";
-        String streamName = "test_multiplication_metrics";
-        DeviceReportLog reportLog = new DeviceReportLog(reportLogName, streamName);
+        String streamName = "test_multiplication";
+        DeviceReportLog reportLog = new DeviceReportLog(REPORT_LOG_NAME, streamName);
         reportLog.addValue(EXPECTED_PRODUCT_TAG, 1.0 * MULTIPLICATION_RESULT, ResultType.NEUTRAL,
                 ResultUnit.NONE);
         reportLog.addValue(ACTUAL_PRODUCT_TAG, 1.0 * product, ResultType.NEUTRAL, ResultUnit.NONE);
@@ -74,7 +79,7 @@
      * Sample test to check counting up.
      */
     public void testCountUp() {
-        String streamName = "test_count_up_metrics";
+        String streamName = "test_count_up";
         countHelper(1, streamName);
     }
 
@@ -82,7 +87,7 @@
      * Sample test to check counting down.
      */
     public void testCountDown() {
-        String streamName = "test_count_down_metrics";
+        String streamName = "test_count_down";
         countHelper(2, streamName);
     }
 
@@ -111,8 +116,7 @@
         }
 
         // Log metrics.
-        String reportLogName = "SampleDeviceTestMetrics";
-        DeviceReportLog reportLog = new DeviceReportLog(reportLogName, streamName);
+        DeviceReportLog reportLog = new DeviceReportLog(REPORT_LOG_NAME, streamName);
         reportLog.addValue(START_TAG, 1.0 * start, ResultType.NEUTRAL, ResultUnit.NONE);
         reportLog.addValue(END_TAG, 1.0 * end, ResultType.NEUTRAL, ResultUnit.NONE);
         reportLog.setSummary(END_TAG, 1.0 * end, ResultType.NEUTRAL, ResultUnit.NONE);
diff --git a/tests/sample/src/android/sample/cts/SampleDeviceResultTest.java b/tests/sample/src/android/sample/cts/SampleDeviceResultTest.java
index f8f7a6b..41384b0 100644
--- a/tests/sample/src/android/sample/cts/SampleDeviceResultTest.java
+++ b/tests/sample/src/android/sample/cts/SampleDeviceResultTest.java
@@ -36,6 +36,11 @@
 public class SampleDeviceResultTest extends ActivityInstrumentationTestCase2<SampleDeviceActivity> {
 
     /**
+     * Name of the report log to store test metrics.
+     */
+    private static final String REPORT_LOG_NAME = "CtsSampleDeviceTestCases";
+
+    /**
      * The number of times to repeat the test.
      */
     private static final int REPEAT = 5;
@@ -76,14 +81,13 @@
         // Compute the stats.
         Stat.StatResult stat = Stat.getStat(result);
         // Create a new report to hold the metrics.
-        String reportLogName = "SampleDeviceTestMetrics";
-        String streamName = "test_sort_metrics";
-        DeviceReportLog reportLog = new DeviceReportLog(reportLogName, streamName);
+        String streamName = "test_sort";
+        DeviceReportLog reportLog = new DeviceReportLog(REPORT_LOG_NAME, streamName);
         // Add the results to the report.
         reportLog.addValues("times", result, ResultType.LOWER_BETTER, ResultUnit.MS);
         reportLog.addValue("min", stat.mMin, ResultType.LOWER_BETTER, ResultUnit.MS);
         reportLog.addValue("max", stat.mMax, ResultType.LOWER_BETTER, ResultUnit.MS);
-        // Every report must have a summary,
+        // Set a summary.
         reportLog.setSummary("average", stat.mAverage, ResultType.LOWER_BETTER, ResultUnit.MS);
         // Submit the report to the given instrumentation.
         reportLog.submit(getInstrumentation());
diff --git a/tests/simplecpu/src/android/simplecpu/cts/SimpleCpuTest.java b/tests/simplecpu/src/android/simplecpu/cts/SimpleCpuTest.java
index fa7f402..6d8984a 100644
--- a/tests/simplecpu/src/android/simplecpu/cts/SimpleCpuTest.java
+++ b/tests/simplecpu/src/android/simplecpu/cts/SimpleCpuTest.java
@@ -38,6 +38,7 @@
     private static final int NUMBER_REPEAT = 20;
     // reject data outside +/- this value * median
     private static final double OUTLIER_THRESHOLD = 0.1;
+    private static final String REPORT_LOG_NAME = "CtsSimpleCpuTestCases";
 
     @Override
     protected void setUp() throws Exception {
@@ -100,13 +101,15 @@
         for (int i = 0; i < numberRepeat; i++) {
             result[i] = CpuNative.runSort(arrayLength, numberRepeatInEachCall);
         }
-        DeviceReportLog report = new DeviceReportLog();
-        report.addValues("sorting time", result, ResultType.LOWER_BETTER, ResultUnit.MS);
+        String streamName = "do_test_sort";
+        DeviceReportLog report = new DeviceReportLog(REPORT_LOG_NAME, streamName);
+        report.addValue("array_length", arrayLength, ResultType.NEUTRAL, ResultUnit.NONE);
+        report.addValues("sorting_time", result, ResultType.LOWER_BETTER, ResultUnit.MS);
         Stat.StatResult stat = Stat.getStatWithOutlierRejection(result, OUTLIER_THRESHOLD);
         if (stat.mDataCount != result.length) {
             Log.w(TAG, "rejecting " + (result.length - stat.mDataCount) + " outliers");
         }
-        report.setSummary("sorting time", stat.mAverage, ResultType.LOWER_BETTER, ResultUnit.MS);
+        report.setSummary("sorting_time_average", stat.mAverage, ResultType.LOWER_BETTER, ResultUnit.MS);
         report.submit(getInstrumentation());
     }
 
@@ -122,14 +125,16 @@
         for (int i = 0; i < numberRepeat; i++) {
             result[i] = CpuNative.runMatrixMultiplication(n, numberRepeatInEachCall);
         }
-        DeviceReportLog report = new DeviceReportLog();
-        report.addValues("matrix mutiplication time", result, ResultType.LOWER_BETTER,
+        String streamName = "do_matrix_multiplication";
+        DeviceReportLog report = new DeviceReportLog(REPORT_LOG_NAME, streamName);
+        report.addValue("matrix_dimension", n, ResultType.NEUTRAL, ResultUnit.NONE);
+        report.addValues("matrix_mutiplication_time", result, ResultType.LOWER_BETTER,
                 ResultUnit.MS);
         Stat.StatResult stat = Stat.getStatWithOutlierRejection(result, OUTLIER_THRESHOLD);
         if (stat.mDataCount != result.length) {
             Log.w(TAG, "rejecting " + (result.length - stat.mDataCount) + " outliers");
         }
-        report.setSummary("matrix mutiplication time", stat.mAverage,
+        report.setSummary("matrix_mutiplication_time_average", stat.mAverage,
                 ResultType.LOWER_BETTER, ResultUnit.MS);
         report.submit(getInstrumentation());
     }
diff --git a/tests/tests/accounts/Android.mk b/tests/tests/accounts/Android.mk
index dc17e0e..31af474 100644
--- a/tests/tests/accounts/Android.mk
+++ b/tests/tests/accounts/Android.mk
@@ -22,7 +22,7 @@
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
-    CtsAccountTestsCommon ctstestrunner platform-test-annotations
+    CtsAccountTestsCommon ctstestrunner
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/tests/content/src/android/content/res/cts/PrivateAttributeTest.java b/tests/tests/content/src/android/content/res/cts/PrivateAttributeTest.java
index 3024896..f80b628 100644
--- a/tests/tests/content/src/android/content/res/cts/PrivateAttributeTest.java
+++ b/tests/tests/content/src/android/content/res/cts/PrivateAttributeTest.java
@@ -26,7 +26,7 @@
  */
 public class PrivateAttributeTest extends AndroidTestCase {
 
-    private static final int sLastPublicAttr = 0x010104f1;
+    private static final int sLastPublicAttr = 0x01010527;
 
     public void testNoAttributesAfterLastPublicAttribute() throws Exception {
         final Resources res = getContext().getResources();
diff --git a/tests/tests/display/Android.mk b/tests/tests/display/Android.mk
index 5de5610..2b81ec1 100644
--- a/tests/tests/display/Android.mk
+++ b/tests/tests/display/Android.mk
@@ -27,7 +27,7 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test platform-test-annotations
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
 
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts
diff --git a/tests/tests/dpi/Android.mk b/tests/tests/dpi/Android.mk
index 3f4cde5..0c158121 100644
--- a/tests/tests/dpi/Android.mk
+++ b/tests/tests/dpi/Android.mk
@@ -17,7 +17,7 @@
 
 include $(CLEAR_VARS)
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner platform-test-annotations
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/tests/draganddrop/AndroidManifest.xml b/tests/tests/draganddrop/AndroidManifest.xml
deleted file mode 100644
index 94540bb..0000000
--- a/tests/tests/draganddrop/AndroidManifest.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.dnd.cts">
-
-    <application>
-        <uses-library android:name="android.test.runner"/>
-    </application>
-
-    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.dnd.cts"/>
-
-</manifest>
\ No newline at end of file
diff --git a/tests/tests/draganddrop/droptarget/Android.mk b/tests/tests/draganddrop/droptarget/Android.mk
deleted file mode 100644
index a729f71..0000000
--- a/tests/tests/draganddrop/droptarget/Android.mk
+++ /dev/null
@@ -1,37 +0,0 @@
-# Copyright (C) 2016 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 $(CLEAR_VARS)
-
-# Don't include this package in any target
-LOCAL_MODULE_TAGS := tests
-# When built, explicitly put it in the data partition.
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-
-LOCAL_DEX_PREOPT := false
-
-LOCAL_PROGUARD_ENABLED := disabled
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts
-
-LOCAL_PACKAGE_NAME := CtsDropTargetApp
-
-LOCAL_SDK_VERSION := current
-
-include $(BUILD_PACKAGE)
\ No newline at end of file
diff --git a/tests/tests/draganddrop/droptarget/res/layout/main_activity.xml b/tests/tests/draganddrop/droptarget/res/layout/main_activity.xml
deleted file mode 100644
index 509debc..0000000
--- a/tests/tests/draganddrop/droptarget/res/layout/main_activity.xml
+++ /dev/null
@@ -1,62 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 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.
--->
-
-<LinearLayout
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="vertical">
-
-    <TextView
-            android:id="@+id/target"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:gravity="center"
-            android:background="#ddd">
-    </TextView>
-
-    <TextView
-            android:id="@+id/result"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginLeft="10dp"
-            android:text="@string/not_available_label">
-    </TextView>
-
-    <TextView
-            android:id="@+id/drag_started"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginLeft="10dp"
-            android:text="@string/not_available_label">
-    </TextView>
-
-    <TextView
-            android:id="@+id/extra_value"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginLeft="10dp"
-            android:text="@string/not_available_label">
-    </TextView>
-
-    <TextView
-            android:id="@+id/details"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginLeft="10dp">
-    </TextView>
-
-</LinearLayout>
\ No newline at end of file
diff --git a/tests/tests/draganddrop/src/android/dnd/cts/DragAndDropTest.java b/tests/tests/draganddrop/src/android/dnd/cts/DragAndDropTest.java
deleted file mode 100644
index e91e9c9..0000000
--- a/tests/tests/draganddrop/src/android/dnd/cts/DragAndDropTest.java
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright (C) 2016 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.dnd.cts;
-
-import android.app.ActivityOptions;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Point;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.UiObject2;
-import android.support.test.uiautomator.Until;
-import android.test.InstrumentationTestCase;
-
-import java.util.concurrent.TimeoutException;
-
-public class DragAndDropTest extends InstrumentationTestCase {
-
-    private static final String DRAG_SOURCE_PKG = "android.dnd.cts.dragsource";
-    private static final String DROP_TARGET_PKG = "android.dnd.cts.droptarget";
-
-    private static final int TIMEOUT = 5000;
-
-    // Constants copied from ActivityManager.StackId. If they are changed there, these must be
-    // updated.
-    public static final int FREEFORM_WORKSPACE_STACK_ID = 1;
-    public static final int DOCKED_STACK_ID = 3;
-
-    private UiDevice mDevice;
-
-    public void setUp() throws TimeoutException {
-        mDevice = UiDevice.getInstance(getInstrumentation());
-    }
-
-    private void startAppInStack(String packageName, int stackId, String mode) {
-        Context context = getInstrumentation().getContext();
-        Intent intent = context.getPackageManager().getLaunchIntentForPackage(packageName);
-        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
-        intent.putExtra("mode", mode);
-
-        ActivityOptions options = ActivityOptions.makeBasic();
-        options.setLaunchStackId(stackId);
-        context.startActivity(intent, options.toBundle());
-
-        // Wait for the app to appear
-        mDevice.wait(Until.hasObject(By.pkg(packageName).depth(0)), TIMEOUT);
-    }
-
-    private Point getVisibleCenter(String packageName, String sourceViewId) {
-        return findObject(packageName, sourceViewId).getVisibleCenter();
-    }
-
-    private UiObject2 findObject(String packageName, String id) {
-        return mDevice.findObject(By.res(packageName, id));
-    }
-
-    private void drag(Point srcPosition, Point tgtPosition) {
-        mDevice.drag(srcPosition.x, srcPosition.y, tgtPosition.x, tgtPosition.y, 100);
-    }
-
-    private void doCrossAppDrag(String sourceMode, String targetMode, String expectedResult) {
-        startAppInStack(DRAG_SOURCE_PKG, DOCKED_STACK_ID, sourceMode);
-        Point srcPosition = getVisibleCenter(DRAG_SOURCE_PKG, "source");
-
-        startAppInStack(DROP_TARGET_PKG, FREEFORM_WORKSPACE_STACK_ID, targetMode);
-        Point tgtPosition = getVisibleCenter(DROP_TARGET_PKG, "target");
-
-        drag(srcPosition, tgtPosition);
-
-        // If we don't do that the next 'findObject' often fails.
-        mDevice.click(tgtPosition.x, tgtPosition.y);
-
-        UiObject2 result = findObject(DROP_TARGET_PKG, "result");
-        assertNotNull("Result widget not found", result);
-        assertEquals(expectedResult, result.getText());
-    }
-
-    private void assertDragStarted(String expectedResult) {
-        UiObject2 drag_started = findObject(DROP_TARGET_PKG, "drag_started");
-        assertEquals(expectedResult, drag_started.getText());
-    }
-
-    private void assertExtraValue(String expectedResult) {
-        UiObject2 extra_value = findObject(DROP_TARGET_PKG, "extra_value");
-        assertEquals(expectedResult, extra_value.getText());
-    }
-
-    public void testLocal() {
-        doCrossAppDrag("disallow_global", "dont_request", "N/A");
-        assertDragStarted("N/A");
-        assertExtraValue("N/A");
-    }
-
-    public void testCancel() {
-        doCrossAppDrag("cancel_soon", "dont_request", "N/A");
-        assertDragStarted("DRAG_STARTED");
-        assertExtraValue("OK");
-    }
-
-    public void testDontGrantDontRequest() {
-        doCrossAppDrag("dont_grant", "dont_request", "Exception");
-        assertDragStarted("DRAG_STARTED");
-        assertExtraValue("OK");
-    }
-
-    public void testDontGrantRequestRead() {
-        doCrossAppDrag("dont_grant", "request_read", "Null DragAndDropPermissions");
-    }
-
-    public void testDontGrantRequestWrite() {
-        doCrossAppDrag("dont_grant", "request_write", "Null DragAndDropPermissions");
-    }
-
-    public void testGrantReadDontRequest() {
-        doCrossAppDrag("grant_read", "dont_request", "Exception");
-    }
-
-    public void testGrantReadRequestRead() {
-        doCrossAppDrag("grant_read", "request_read", "OK");
-    }
-
-    public void testGrantReadRequestWrite() {
-        doCrossAppDrag("grant_read", "request_write", "Exception");
-    }
-
-    public void testGrantWriteDontRequest() {
-        doCrossAppDrag("grant_write", "dont_request", "Exception");
-    }
-
-    public void testGrantWriteRequestRead() {
-        doCrossAppDrag("grant_write", "request_read", "Exception");
-    }
-
-    public void testGrantWriteRequestWrite() {
-        doCrossAppDrag("grant_write", "request_write", "OK");
-    }
-
-    public void testGrantReadPrefixRequestReadNested() {
-        doCrossAppDrag("grant_read_prefix", "request_read_nested", "OK");
-    }
-
-    public void testGrantReadNoPrefixRequestReadNested() {
-        doCrossAppDrag("grant_read_noprefix", "request_read_nested", "Exception");
-    }
-
-    public void testGrantPersistableRequestTakePersistable() {
-        doCrossAppDrag("grant_read_persistable", "request_take_persistable", "OK");
-    }
-
-    public void testGrantReadRequestTakePersistable() {
-        doCrossAppDrag("grant_read", "request_take_persistable", "Exception");
-    }
-}
diff --git a/tests/tests/hardware/Android.mk b/tests/tests/hardware/Android.mk
index c38a751..9b3969c 100644
--- a/tests/tests/hardware/Android.mk
+++ b/tests/tests/hardware/Android.mk
@@ -24,8 +24,9 @@
 
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    ctsdeviceutil compatibility-device-util platform-test-annotations
+LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil
+
+LOCAL_JAVA_LIBRARIES := platform-test-annotations
 
 LOCAL_SDK_VERSION := current
 
@@ -59,8 +60,7 @@
     compatibility-device-util \
     ctstestrunner \
     mockito-target \
-    android-ex-camera2 \
-    platform-test-annotations
+    android-ex-camera2
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-renderscript-files-under, src)
 
diff --git a/tests/tests/keystore/Android.mk b/tests/tests/keystore/Android.mk
index fe98bc3..4688846 100644
--- a/tests/tests/keystore/Android.mk
+++ b/tests/tests/keystore/Android.mk
@@ -29,8 +29,7 @@
         core-tests-support \
         ctsdeviceutil \
         ctstestrunner \
-        guava \
-        platform-test-annotations
+        guava
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/tests/media/res/raw/video_1280x720_mp4_hevc_1150kbps_30fps_aac_stereo_128kbps_48000hz.mp4 b/tests/tests/media/res/raw/video_1280x720_mp4_hevc_1150kbps_30fps_aac_stereo_128kbps_48000hz.mp4
index 9bf2124..29408c58 100644
--- a/tests/tests/media/res/raw/video_1280x720_mp4_hevc_1150kbps_30fps_aac_stereo_128kbps_48000hz.mp4
+++ b/tests/tests/media/res/raw/video_1280x720_mp4_hevc_1150kbps_30fps_aac_stereo_128kbps_48000hz.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/video_1280x720_webm_vp9_csd_309kbps_25fps_vorbis_stereo_128kbps_48000hz.webm b/tests/tests/media/res/raw/video_1280x720_webm_vp9_csd_309kbps_25fps_vorbis_stereo_128kbps_48000hz.webm
new file mode 100644
index 0000000..8f00ded
--- /dev/null
+++ b/tests/tests/media/res/raw/video_1280x720_webm_vp9_csd_309kbps_25fps_vorbis_stereo_128kbps_48000hz.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/video_176x144_mp4_mpeg2_105kbps_25fps_aac_stereo_128kbps_44100hz.mp4 b/tests/tests/media/res/raw/video_176x144_mp4_mpeg2_105kbps_25fps_aac_stereo_128kbps_44100hz.mp4
new file mode 100644
index 0000000..7875ff9
--- /dev/null
+++ b/tests/tests/media/res/raw/video_176x144_mp4_mpeg2_105kbps_25fps_aac_stereo_128kbps_44100hz.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/video_256x144_webm_vp9_hdr_83kbps_24fps.webm b/tests/tests/media/res/raw/video_256x144_webm_vp9_hdr_83kbps_24fps.webm
new file mode 100644
index 0000000..bc4ef33
--- /dev/null
+++ b/tests/tests/media/res/raw/video_256x144_webm_vp9_hdr_83kbps_24fps.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/video_480x360_mp4_hevc_325kbps_30fps_aac_stereo_128kbps_48000hz.mp4 b/tests/tests/media/res/raw/video_480x360_mp4_hevc_325kbps_30fps_aac_stereo_128kbps_48000hz.mp4
index e3e3ef01..fbf714a 100644
--- a/tests/tests/media/res/raw/video_480x360_mp4_hevc_325kbps_30fps_aac_stereo_128kbps_48000hz.mp4
+++ b/tests/tests/media/res/raw/video_480x360_mp4_hevc_325kbps_30fps_aac_stereo_128kbps_48000hz.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/video_480x360_mp4_hevc_650kbps_30fps_aac_stereo_128kbps_48000hz.mp4 b/tests/tests/media/res/raw/video_480x360_mp4_hevc_650kbps_30fps_aac_stereo_128kbps_48000hz.mp4
index 4eb6161..8d69e41 100644
--- a/tests/tests/media/res/raw/video_480x360_mp4_hevc_650kbps_30fps_aac_stereo_128kbps_48000hz.mp4
+++ b/tests/tests/media/res/raw/video_480x360_mp4_hevc_650kbps_30fps_aac_stereo_128kbps_48000hz.mp4
Binary files differ
diff --git a/tests/tests/media/src/android/media/cts/AudioRecordTest.java b/tests/tests/media/src/android/media/cts/AudioRecordTest.java
index afd6997..8c25186 100644
--- a/tests/tests/media/src/android/media/cts/AudioRecordTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioRecordTest.java
@@ -43,6 +43,7 @@
 
 public class AudioRecordTest extends CtsAndroidTestCase {
     private final static String TAG = "AudioRecordTest";
+    private static final String REPORT_LOG_NAME = "CtsMediaTestCases";
     private AudioRecord mAudioRecord;
     private int mHz = 44100;
     private boolean mIsOnMarkerReachedCalled;
@@ -235,7 +236,7 @@
     }
 
     public void testAudioRecordResamplerMono8Bit() throws Exception {
-        doTest("ResamplerResamplerMono8Bit", true /*localRecord*/, false /*customHandler*/,
+        doTest("resampler_mono_8bit", true /*localRecord*/, false /*customHandler*/,
                 1 /*periodsPerSecond*/, 1 /*markerPeriodsPerSecond*/,
                 false /*useByteBuffer*/,  false /*blocking*/,
                 false /*auditRecording*/, false /*isChannelIndex*/, 88200 /*TEST_SR*/,
@@ -243,7 +244,7 @@
     }
 
     public void testAudioRecordResamplerStereo8Bit() throws Exception {
-        doTest("ResamplerStereo8Bit", true /*localRecord*/, false /*customHandler*/,
+        doTest("resampler_stereo_8bit", true /*localRecord*/, false /*customHandler*/,
                 0 /*periodsPerSecond*/, 3 /*markerPeriodsPerSecond*/,
                 true /*useByteBuffer*/,  true /*blocking*/,
                 false /*auditRecording*/, false /*isChannelIndex*/, 45000 /*TEST_SR*/,
@@ -251,7 +252,7 @@
     }
 
     public void testAudioRecordLocalMono16Bit() throws Exception {
-        doTest("LocalMono16Bit", true /*localRecord*/, false /*customHandler*/,
+        doTest("local_mono_16bit", true /*localRecord*/, false /*customHandler*/,
                 30 /*periodsPerSecond*/, 2 /*markerPeriodsPerSecond*/,
                 false /*useByteBuffer*/, true /*blocking*/,
                 false /*auditRecording*/, false /*isChannelIndex*/, 8000 /*TEST_SR*/,
@@ -259,7 +260,7 @@
     }
 
     public void testAudioRecordStereo16Bit() throws Exception {
-        doTest("Stereo16Bit", false /*localRecord*/, false /*customHandler*/,
+        doTest("stereo_16bit", false /*localRecord*/, false /*customHandler*/,
                 2 /*periodsPerSecond*/, 2 /*markerPeriodsPerSecond*/,
                 false /*useByteBuffer*/, false /*blocking*/,
                 false /*auditRecording*/, false /*isChannelIndex*/, 17000 /*TEST_SR*/,
@@ -267,7 +268,7 @@
     }
 
     public void testAudioRecordMonoFloat() throws Exception {
-        doTest("MonoFloat", false /*localRecord*/, true /*customHandler*/,
+        doTest("mono_float", false /*localRecord*/, true /*customHandler*/,
                 30 /*periodsPerSecond*/, 2 /*markerPeriodsPerSecond*/,
                 false /*useByteBuffer*/, true /*blocking*/,
                 false /*auditRecording*/, false /*isChannelIndex*/, 32000 /*TEST_SR*/,
@@ -275,7 +276,7 @@
     }
 
     public void testAudioRecordLocalNonblockingStereoFloat() throws Exception {
-        doTest("LocalNonblockingStereoFloat", true /*localRecord*/, true /*customHandler*/,
+        doTest("local_nonblocking_stereo_float", true /*localRecord*/, true /*customHandler*/,
                 2 /*periodsPerSecond*/, 0 /*markerPeriodsPerSecond*/,
                 false /*useByteBuffer*/, false /*blocking*/,
                 false /*auditRecording*/, false /*isChannelIndex*/, 48000 /*TEST_SR*/,
@@ -287,7 +288,7 @@
         if (isLowRamDevice()) {
             return; // skip. FIXME: reenable when AF memory allocation is updated.
         }
-        doTest("AuditByteBufferResamplerStereoFloat",
+        doTest("audit_byte_buffer_resampler_stereo_float",
                 false /*localRecord*/, true /*customHandler*/,
                 2 /*periodsPerSecond*/, 0 /*markerPeriodsPerSecond*/,
                 true /*useByteBuffer*/, false /*blocking*/,
@@ -296,7 +297,7 @@
     }
 
     public void testAudioRecordAuditChannelIndexMonoFloat() throws Exception {
-        doTest("AuditChannelIndexMonoFloat", true /*localRecord*/, true /*customHandler*/,
+        doTest("audit_channel_index_mono_float", true /*localRecord*/, true /*customHandler*/,
                 2 /*periodsPerSecond*/, 0 /*markerPeriodsPerSecond*/,
                 false /*useByteBuffer*/, false /*blocking*/,
                 true /*auditRecording*/, true /*isChannelIndex*/, 47000 /*TEST_SR*/,
@@ -309,7 +310,7 @@
         if (isLowRamDevice()) {
             return; // skip. FIXME: reenable when AF memory allocation is updated.
         }
-        doTest("AuditChannelIndex2", true /*localRecord*/, true /*customHandler*/,
+        doTest("audit_channel_index_2", true /*localRecord*/, true /*customHandler*/,
                 2 /*periodsPerSecond*/, 0 /*markerPeriodsPerSecond*/,
                 false /*useByteBuffer*/, false /*blocking*/,
                 true /*auditRecording*/, true /*isChannelIndex*/, 192000 /*TEST_SR*/,
@@ -320,7 +321,7 @@
     // Audit buffers can run out of space with high numbers of channels,
     // so keep the sample rate low.
     public void testAudioRecordAuditChannelIndex5() throws Exception {
-        doTest("AuditChannelIndex5", true /*localRecord*/, true /*customHandler*/,
+        doTest("audit_channel_index_5", true /*localRecord*/, true /*customHandler*/,
                 2 /*periodsPerSecond*/, 0 /*markerPeriodsPerSecond*/,
                 false /*useByteBuffer*/, false /*blocking*/,
                 true /*auditRecording*/, true /*isChannelIndex*/, 16000 /*TEST_SR*/,
@@ -1115,37 +1116,33 @@
         }
 
         // report this
-        DeviceReportLog log = new DeviceReportLog();
-        log.addValue(reportName + ": startRecording lag", coldInputStartTime,
-                ResultType.LOWER_BETTER, ResultUnit.MS);
-        log.addValue(reportName + ": stop execution time", stopTime - stopRequestTime,
-                ResultType.LOWER_BETTER, ResultUnit.MS);
-        log.addValue(reportName + ": Total record time expected", TEST_TIME_MS,
-                ResultType.NEUTRAL, ResultUnit.MS);
-        log.addValue(reportName + ": Total record time actual", endTime - firstSampleTime,
-                ResultType.NEUTRAL, ResultUnit.MS);
-        log.addValue(reportName + ": Total markers expected", markerPeriods,
-                ResultType.NEUTRAL, ResultUnit.COUNT);
-        log.addValue(reportName + ": Total markers actual", markerList.size(),
-                ResultType.NEUTRAL, ResultUnit.COUNT);
-        log.addValue(reportName + ": Total periods expected", updatePeriods,
-                ResultType.NEUTRAL, ResultUnit.COUNT);
-        log.addValue(reportName + ": Total periods actual", periodicList.size(),
-                ResultType.NEUTRAL, ResultUnit.COUNT);
-        log.addValue(reportName + ": Average Marker diff", markerStat.getAvg(),
-                ResultType.LOWER_BETTER, ResultUnit.MS);
-        log.addValue(reportName + ": Maximum Marker abs diff", markerStat.getMaxAbs(),
-                ResultType.LOWER_BETTER, ResultUnit.MS);
-        log.addValue(reportName + ": Average Marker abs diff", markerStat.getAvgAbs(),
-                ResultType.LOWER_BETTER, ResultUnit.MS);
-        log.addValue(reportName + ": Average Periodic diff", periodicStat.getAvg(),
-                ResultType.LOWER_BETTER, ResultUnit.MS);
-        log.addValue(reportName + ": Maximum Periodic abs diff", periodicStat.getMaxAbs(),
-                ResultType.LOWER_BETTER, ResultUnit.MS);
-        log.addValue(reportName + ": Average Periodic abs diff", periodicStat.getAvgAbs(),
-                ResultType.LOWER_BETTER, ResultUnit.MS);
-        log.setSummary(reportName + ": Unified abs diff",
-                (periodicStat.getAvgAbs() + markerStat.getAvgAbs()) / 2,
+        DeviceReportLog log = new DeviceReportLog(REPORT_LOG_NAME, reportName);
+        log.addValue("start_recording_lag", coldInputStartTime, ResultType.LOWER_BETTER,
+                ResultUnit.MS);
+        log.addValue("stop_execution_time", stopTime - stopRequestTime, ResultType.LOWER_BETTER,
+                ResultUnit.MS);
+        log.addValue("total_record_time_expected", TEST_TIME_MS, ResultType.NEUTRAL, ResultUnit.MS);
+        log.addValue("total_record_time_actual", endTime - firstSampleTime, ResultType.NEUTRAL,
+                ResultUnit.MS);
+        log.addValue("total_markers_expected", markerPeriods, ResultType.NEUTRAL, ResultUnit.COUNT);
+        log.addValue("total_markers_actual", markerList.size(), ResultType.NEUTRAL,
+                ResultUnit.COUNT);
+        log.addValue("total_periods_expected", updatePeriods, ResultType.NEUTRAL, ResultUnit.COUNT);
+        log.addValue("total_periods_actual", periodicList.size(), ResultType.NEUTRAL,
+                ResultUnit.COUNT);
+        log.addValue("average_marker_diff", markerStat.getAvg(), ResultType.LOWER_BETTER,
+                ResultUnit.MS);
+        log.addValue("maximum_marker_abs_diff", markerStat.getMaxAbs(), ResultType.LOWER_BETTER,
+                ResultUnit.MS);
+        log.addValue("average_marker_abs_diff", markerStat.getAvgAbs(), ResultType.LOWER_BETTER,
+                ResultUnit.MS);
+        log.addValue("average_periodic_diff", periodicStat.getAvg(), ResultType.LOWER_BETTER,
+                ResultUnit.MS);
+        log.addValue("maximum_periodic_abs_diff", periodicStat.getMaxAbs(), ResultType.LOWER_BETTER,
+                ResultUnit.MS);
+        log.addValue("average_periodic_abs_diff", periodicStat.getAvgAbs(), ResultType.LOWER_BETTER,
+                ResultUnit.MS);
+        log.setSummary("unified_abs_diff", (periodicStat.getAvgAbs() + markerStat.getAvgAbs()) / 2,
                 ResultType.LOWER_BETTER, ResultUnit.MS);
         log.submit(getInstrumentation());
     }
diff --git a/tests/tests/media/src/android/media/cts/AudioTrackLatencyTest.java b/tests/tests/media/src/android/media/cts/AudioTrackLatencyTest.java
index 5aa180c..cf1a406 100644
--- a/tests/tests/media/src/android/media/cts/AudioTrackLatencyTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioTrackLatencyTest.java
@@ -28,10 +28,6 @@
 import android.media.PlaybackParams;
 import android.util.Log;
 
-import com.android.compatibility.common.util.DeviceReportLog;
-import com.android.compatibility.common.util.ResultType;
-import com.android.compatibility.common.util.ResultUnit;
-
 import java.nio.ByteBuffer;
 import java.nio.FloatBuffer;
 import java.nio.ShortBuffer;
diff --git a/tests/tests/media/src/android/media/cts/AudioTrackTest.java b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
index d81c11c..fb64c8e 100644
--- a/tests/tests/media/src/android/media/cts/AudioTrackTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
@@ -41,6 +41,7 @@
     private final long WAIT_MSEC = 200;
     private final int OFFSET_DEFAULT = 0;
     private final int OFFSET_NEGATIVE = -10;
+    private static final String REPORT_LOG_NAME = "CtsMediaTestCases";
 
     private void log(String testName, String message) {
         Log.v(TAG, "[" + testName + "] " + message);
@@ -1911,11 +1912,13 @@
                     + "audio output HAL");
             return;
         }
+        String streamName = "test_get_timestamp";
         doTestTimestamp(
                 22050 /* sampleRate */,
                 AudioFormat.CHANNEL_OUT_MONO ,
                 AudioFormat.ENCODING_PCM_16BIT,
-                AudioTrack.MODE_STREAM);
+                AudioTrack.MODE_STREAM,
+                streamName);
     }
 
     public void testFastTimestamp() throws Exception {
@@ -1924,15 +1927,17 @@
                     + "audio output HAL");
             return;
         }
+        String streamName = "test_fast_timestamp";
         doTestTimestamp(
                 AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_MUSIC),
                 AudioFormat.CHANNEL_OUT_MONO,
                 AudioFormat.ENCODING_PCM_16BIT,
-                AudioTrack.MODE_STREAM);
+                AudioTrack.MODE_STREAM,
+                streamName);
     }
 
-    private void doTestTimestamp(
-            int sampleRate, int channelMask, int encoding, int transferMode) throws Exception {
+    private void doTestTimestamp(int sampleRate, int channelMask, int encoding, int transferMode,
+            String streamName) throws Exception {
         // constants for test
         final String TEST_NAME = "testGetTimestamp";
         final int TEST_LOOP_CNT = 10;
@@ -2069,14 +2074,14 @@
         track.release();
         // Log the average jitter
         if (cumulativeJitterCount > 0) {
-            DeviceReportLog log = new DeviceReportLog();
+            DeviceReportLog log = new DeviceReportLog(REPORT_LOG_NAME, streamName);
             final float averageJitterInFrames = cumulativeJitter / cumulativeJitterCount;
             final float averageJitterInMs = averageJitterInFrames * 1000 / sampleRate;
             final float maxJitterInMs = maxJitter * 1000 / sampleRate;
             // ReportLog needs at least one Value and Summary.
-            log.addValue("Maximum Jitter", maxJitterInMs,
+            log.addValue("maximum_jitter", maxJitterInMs,
                     ResultType.LOWER_BETTER, ResultUnit.MS);
-            log.setSummary("Average Jitter", averageJitterInMs,
+            log.setSummary("average_jitter", averageJitterInMs,
                     ResultType.LOWER_BETTER, ResultUnit.MS);
             log.submit(getInstrumentation());
         }
diff --git a/tests/tests/media/src/android/media/cts/AudioTrack_ListenerTest.java b/tests/tests/media/src/android/media/cts/AudioTrack_ListenerTest.java
index 37affd0..971bfb3 100644
--- a/tests/tests/media/src/android/media/cts/AudioTrack_ListenerTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioTrack_ListenerTest.java
@@ -33,6 +33,7 @@
 
 public class AudioTrack_ListenerTest extends CtsAndroidTestCase {
     private final static String TAG = "AudioTrack_ListenerTest";
+    private static final String REPORT_LOG_NAME = "CtsMediaTestCases";
     private final static int TEST_SR = 11025;
     private final static int TEST_CONF = AudioFormat.CHANNEL_OUT_MONO;
     private final static int TEST_FORMAT = AudioFormat.ENCODING_PCM_8BIT;
@@ -53,25 +54,26 @@
     };
 
     public void testAudioTrackCallback() throws Exception {
-        doTest("Streaming Local Looper", true /*localTrack*/, false /*customHandler*/,
+        doTest("streaming_local_looper", true /*localTrack*/, false /*customHandler*/,
                 30 /*periodsPerSecond*/, 2 /*markerPeriodsPerSecond*/, AudioTrack.MODE_STREAM);
     }
 
     public void testAudioTrackCallbackWithHandler() throws Exception {
         // with 100 periods per second, trigger back-to-back notifications.
-        doTest("Streaming Private Handler", false /*localTrack*/, true /*customHandler*/,
+        doTest("streaming_private_handler", false /*localTrack*/, true /*customHandler*/,
                 100 /*periodsPerSecond*/, 10 /*markerPeriodsPerSecond*/, AudioTrack.MODE_STREAM);
         // verify mHandler is used only for accessing its associated Looper
         assertFalse(mIsHandleMessageCalled);
     }
 
     public void testStaticAudioTrackCallback() throws Exception {
-        doTest("Static", false /*localTrack*/, false /*customHandler*/,
+        doTest("static", false /*localTrack*/, false /*customHandler*/,
                 100 /*periodsPerSecond*/, 10 /*markerPeriodsPerSecond*/, AudioTrack.MODE_STATIC);
     }
 
     public void testStaticAudioTrackCallbackWithHandler() throws Exception {
-        doTest("Static Private Handler", false /*localTrack*/, true /*customHandler*/,
+        String streamName = "test_static_audio_track_callback_handler";
+        doTest("static_private_handler", false /*localTrack*/, true /*customHandler*/,
                 30 /*periodsPerSecond*/, 2 /*markerPeriodsPerSecond*/, AudioTrack.MODE_STATIC);
         // verify mHandler is used only for accessing its associated Looper
         assertFalse(mIsHandleMessageCalled);
@@ -205,21 +207,20 @@
         }
 
         // report this
-        DeviceReportLog log = new DeviceReportLog();
-        log.addValue(reportName + ": Average Marker diff", markerStat.getAvg(),
-                ResultType.LOWER_BETTER, ResultUnit.MS);
-        log.addValue(reportName + ": Maximum Marker abs diff", markerStat.getMaxAbs(),
-                ResultType.LOWER_BETTER, ResultUnit.MS);
-        log.addValue(reportName + ": Average Marker abs diff", markerStat.getAvgAbs(),
-                ResultType.LOWER_BETTER, ResultUnit.MS);
-        log.addValue(reportName + ": Average Periodic diff", periodicStat.getAvg(),
-                ResultType.LOWER_BETTER, ResultUnit.MS);
-        log.addValue(reportName + ": Maximum Periodic abs diff", periodicStat.getMaxAbs(),
-                ResultType.LOWER_BETTER, ResultUnit.MS);
-        log.addValue(reportName + ": Average Periodic abs diff", periodicStat.getAvgAbs(),
-                ResultType.LOWER_BETTER, ResultUnit.MS);
-        log.setSummary(reportName + ": Unified abs diff",
-                (periodicStat.getAvgAbs() + markerStat.getAvgAbs()) / 2,
+        DeviceReportLog log = new DeviceReportLog(REPORT_LOG_NAME, reportName);
+        log.addValue("average_marker_diff", markerStat.getAvg(), ResultType.LOWER_BETTER,
+                ResultUnit.MS);
+        log.addValue("maximum_marker_abs_diff", markerStat.getMaxAbs(), ResultType.LOWER_BETTER,
+                ResultUnit.MS);
+        log.addValue("average_marker_abs_diff", markerStat.getAvgAbs(), ResultType.LOWER_BETTER,
+                ResultUnit.MS);
+        log.addValue("average_periodic_diff", periodicStat.getAvg(), ResultType.LOWER_BETTER,
+                ResultUnit.MS);
+        log.addValue("maximum_periodic_abs_diff", periodicStat.getMaxAbs(), ResultType.LOWER_BETTER,
+                ResultUnit.MS);
+        log.addValue("average_periodic_abs_diff", periodicStat.getAvgAbs(), ResultType.LOWER_BETTER,
+                ResultUnit.MS);
+        log.setSummary("unified_abs_diff", (periodicStat.getAvgAbs() + markerStat.getAvgAbs()) / 2,
                 ResultType.LOWER_BETTER, ResultUnit.MS);
         log.submit(getInstrumentation());
     }
diff --git a/tests/tests/media/src/android/media/cts/DecoderTest.java b/tests/tests/media/src/android/media/cts/DecoderTest.java
index 33583be..15ac82f 100755
--- a/tests/tests/media/src/android/media/cts/DecoderTest.java
+++ b/tests/tests/media/src/android/media/cts/DecoderTest.java
@@ -47,6 +47,7 @@
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
 import java.util.zip.CRC32;
 import java.util.concurrent.TimeUnit;
@@ -1930,9 +1931,9 @@
                 testFd.getLength());
         extractor.selectTrack(0);
 
-        int numframes = decodeWithChecks(extractor, CHECKFLAG_RETURN_OUTPUTFRAMES
-                | CHECKFLAG_COMPAREINPUTOUTPUTPTSMATCH, resetMode, s,
-                eosframe, null, null);
+        int numframes = decodeWithChecks(null /* decoderName */, extractor,
+                CHECKFLAG_RETURN_OUTPUTFRAMES | CHECKFLAG_COMPAREINPUTOUTPUTPTSMATCH,
+                resetMode, s, eosframe, null, null);
 
         extractor.release();
         testFd.close();
@@ -1949,7 +1950,8 @@
         extractor.selectTrack(0);
 
         // fails CHECKFLAG_COMPAREINPUTOUTPUTPTSMATCH
-        int outputSize = decodeWithChecks(extractor, CHECKFLAG_RETURN_OUTPUTSIZE, resetMode, null,
+        int outputSize = decodeWithChecks(null /* decoderName */, extractor,
+                CHECKFLAG_RETURN_OUTPUTSIZE, resetMode, null,
                 eosframe, null, null);
 
         extractor.release();
@@ -1957,10 +1959,16 @@
         return outputSize;
     }
 
+    /*
+    * Test all decoders' EOS behavior.
+    */
     private void testEOSBehavior(int movie, int stopatsample) throws Exception {
         testEOSBehavior(movie, new int[] {stopatsample});
     }
 
+    /*
+    * Test all decoders' EOS behavior.
+    */
     private void testEOSBehavior(int movie, int[] stopAtSample) throws Exception {
         Surface s = null;
         AssetFileDescriptor testFd = mResources.openRawResourceFd(movie);
@@ -1969,42 +1977,45 @@
                 testFd.getLength());
         extractor.selectTrack(0); // consider variable looping on track
         MediaFormat format = extractor.getTrackFormat(0);
-        if (!MediaUtils.checkDecoderForFormat(format)) {
-            return; // skip
-        }
-        List<Long> outputChecksums = new ArrayList<Long>();
-        List<Long> outputTimestamps = new ArrayList<Long>();
-        Arrays.sort(stopAtSample);
-        int last = stopAtSample.length - 1;
 
-        // decode reference (longest sequence to stop at + 100) and
-        // store checksums/pts in outputChecksums and outputTimestamps
-        // (will fail CHECKFLAG_COMPAREINPUTOUTPUTSAMPLEMATCH)
-        decodeWithChecks(extractor,
-                CHECKFLAG_SETCHECKSUM | CHECKFLAG_SETPTS | CHECKFLAG_COMPAREINPUTOUTPUTPTSMATCH,
-                RESET_MODE_NONE, s,
-                stopAtSample[last] + 100, outputChecksums, outputTimestamps);
+        Collection<String> decoderNames = MediaUtils.getDecodersForFormat(format);
+        for (String decoderName: decoderNames) {
+            List<Long> outputChecksums = new ArrayList<Long>();
+            List<Long> outputTimestamps = new ArrayList<Long>();
+            Arrays.sort(stopAtSample);
+            int last = stopAtSample.length - 1;
 
-        // decode stopAtSample requests in reverse order (longest to
-        // shortest) and compare to reference checksums/pts in
-        // outputChecksums and outputTimestamps
-        for (int i = last; i >= 0; --i) {
-            if (true) { // reposition extractor
-                extractor.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
-            } else { // create new extractor
-                extractor.release();
-                extractor = new MediaExtractor();
-                extractor.setDataSource(testFd.getFileDescriptor(),
-                        testFd.getStartOffset(), testFd.getLength());
-                extractor.selectTrack(0); // consider variable looping on track
-            }
-            decodeWithChecks(extractor,
-                    CHECKFLAG_COMPARECHECKSUM | CHECKFLAG_COMPAREPTS
-                    | CHECKFLAG_COMPAREINPUTOUTPUTSAMPLEMATCH
-                    | CHECKFLAG_COMPAREINPUTOUTPUTPTSMATCH,
+            // decode reference (longest sequence to stop at + 100) and
+            // store checksums/pts in outputChecksums and outputTimestamps
+            // (will fail CHECKFLAG_COMPAREINPUTOUTPUTSAMPLEMATCH)
+            decodeWithChecks(decoderName, extractor,
+                    CHECKFLAG_SETCHECKSUM | CHECKFLAG_SETPTS | CHECKFLAG_COMPAREINPUTOUTPUTPTSMATCH,
                     RESET_MODE_NONE, s,
-                    stopAtSample[i], outputChecksums, outputTimestamps);
+                    stopAtSample[last] + 100, outputChecksums, outputTimestamps);
+
+            // decode stopAtSample requests in reverse order (longest to
+            // shortest) and compare to reference checksums/pts in
+            // outputChecksums and outputTimestamps
+            for (int i = last; i >= 0; --i) {
+                if (true) { // reposition extractor
+                    extractor.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
+                } else { // create new extractor
+                    extractor.release();
+                    extractor = new MediaExtractor();
+                    extractor.setDataSource(testFd.getFileDescriptor(),
+                            testFd.getStartOffset(), testFd.getLength());
+                    extractor.selectTrack(0); // consider variable looping on track
+                }
+                decodeWithChecks(decoderName, extractor,
+                        CHECKFLAG_COMPARECHECKSUM | CHECKFLAG_COMPAREPTS
+                        | CHECKFLAG_COMPAREINPUTOUTPUTSAMPLEMATCH
+                        | CHECKFLAG_COMPAREINPUTOUTPUTPTSMATCH,
+                        RESET_MODE_NONE, s,
+                        stopAtSample[i], outputChecksums, outputTimestamps);
+            }
+            extractor.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
         }
+
         extractor.release();
         testFd.close();
     }
@@ -2020,10 +2031,13 @@
 
     /**
      * Decodes frames with parameterized checks and return values.
+     * If decoderName is provided, mediacodec will create that decoder. Otherwise,
+     * mediacodec will use the default decoder provided by platform.
      * The integer return can be selected through the checkFlags variable.
      */
-    private static int decodeWithChecks(MediaExtractor extractor, int checkFlags, int resetMode,
-            Surface surface, int stopAtSample,
+    private static int decodeWithChecks(
+            String decoderName, MediaExtractor extractor,
+            int checkFlags, int resetMode, Surface surface, int stopAtSample,
             List<Long> outputChecksums, List<Long> outputTimestamps)
             throws Exception {
         int trackIndex = extractor.getSampleTrackIndex();
@@ -2033,7 +2047,8 @@
         ByteBuffer[] codecInputBuffers;
         ByteBuffer[] codecOutputBuffers;
 
-        MediaCodec codec = createDecoder(format);
+        MediaCodec codec =
+                decoderName == null ? createDecoder(format) : MediaCodec.createByCodecName(decoderName);
         Log.i("@@@@", "using codec: " + codec.getName());
         codec.configure(format, surface, null /* crypto */, 0 /* flags */);
         codec.start();
@@ -2253,37 +2268,42 @@
 
     public void testEOSBehaviorH264() throws Exception {
         // this video has an I frame at 44
-        testEOSBehavior(R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz,
-                new int[] {44, 45, 55});
+        testEOSBehavior(
+                R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz,
+                new int[] {1, 44, 45, 55});
     }
     public void testEOSBehaviorHEVC() throws Exception {
-        testEOSBehavior(R.raw.video_480x360_mp4_hevc_650kbps_30fps_aac_stereo_128kbps_48000hz, 17);
-        testEOSBehavior(R.raw.video_480x360_mp4_hevc_650kbps_30fps_aac_stereo_128kbps_48000hz, 23);
-        testEOSBehavior(R.raw.video_480x360_mp4_hevc_650kbps_30fps_aac_stereo_128kbps_48000hz, 49);
+        testEOSBehavior(
+            R.raw.video_480x360_mp4_hevc_650kbps_30fps_aac_stereo_128kbps_48000hz,
+            new int[] {1, 17, 23, 49});
     }
 
     public void testEOSBehaviorH263() throws Exception {
         // this video has an I frame every 12 frames.
-        testEOSBehavior(R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz,
-                new int[] {24, 25, 48, 50});
+        testEOSBehavior(
+                R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz,
+                new int[] {1, 24, 25, 48, 50});
     }
 
     public void testEOSBehaviorMpeg4() throws Exception {
         // this video has an I frame every 12 frames
-        testEOSBehavior(R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz,
-                new int[] {24, 25, 48, 50, 2});
+        testEOSBehavior(
+                R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz,
+                new int[] {1, 24, 25, 48, 50, 2});
     }
 
     public void testEOSBehaviorVP8() throws Exception {
         // this video has an I frame at 46
-        testEOSBehavior(R.raw.video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_48000hz,
-                new int[] {46, 47, 57, 45});
+        testEOSBehavior(
+                R.raw.video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_48000hz,
+                new int[] {1, 46, 47, 57, 45});
     }
 
     public void testEOSBehaviorVP9() throws Exception {
         // this video has an I frame at 44
-        testEOSBehavior(R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz,
-                new int[] {44, 45, 55, 43});
+        testEOSBehavior(
+                R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz,
+                new int[] {1, 44, 45, 55, 43});
     }
 
     /* from EncodeDecodeTest */
diff --git a/tests/tests/media/src/android/media/cts/EncoderTest.java b/tests/tests/media/src/android/media/cts/EncoderTest.java
index e48af7e..72f090e 100644
--- a/tests/tests/media/src/android/media/cts/EncoderTest.java
+++ b/tests/tests/media/src/android/media/cts/EncoderTest.java
@@ -187,11 +187,9 @@
             57821391502855l,  // fail @ 32000 in zero-lead mode
     };
 
-    private Random mRandom = new Random(1);
-
     private int queueInputBuffer(
             MediaCodec codec, ByteBuffer[] inputBuffers, int index,
-            InputStream istream, int mode, long timeUs) {
+            InputStream istream, int mode, long timeUs, Random random) {
         ByteBuffer buffer = inputBuffers[index];
         buffer.rewind();
         int size = buffer.limit();
@@ -218,8 +216,8 @@
             }
             while (true) {
                 try {
-                    int next = mRandom.nextInt();
-                    buffer.putInt(mRandom.nextInt());
+                    int next = random.nextInt();
+                    buffer.putInt(random.nextInt());
                 } catch (BufferOverflowException ex) {
                     break;
                 }
@@ -295,8 +293,9 @@
         if (sSaveResults) {
             try {
                 String outFile = "/data/local/tmp/transcoded-" + componentName +
-                        "-" + sampleRate + "-" + channelCount + "-" + outBitrate +
-                        "-" + mode + "-" + startSeed + ".mp4";
+                        "-" + sampleRate + "Hz-" + channelCount + "ch-" + outBitrate +
+                        "bps-" + mode + "-" + resid + "-" + startSeed + "-" +
+                        (android.os.Process.is64Bit() ? "64bit" : "32bit") + ".mp4";
                 new File("outFile").delete();
                 muxer = new MediaMuxer(outFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
                 // The track can't be added until we have the codec specific data
@@ -310,7 +309,7 @@
             istream = mContext.getResources().openRawResource(resid);
         }
 
-        mRandom.setSeed(startSeed);
+        Random random = new Random(startSeed);
         MediaCodec codec;
         try {
             codec = MediaCodec.createByCodecName(componentName);
@@ -360,7 +359,7 @@
                         doneSubmittingInput = true;
                     } else {
                         int size = queueInputBuffer(
-                                codec, codecInputBuffers, index, istream, mode, timeUs);
+                                codec, codecInputBuffers, index, istream, mode, timeUs, random);
 
                         numBytesSubmitted += size;
 
diff --git a/tests/tests/media/src/android/media/cts/NativeDecoderTest.java b/tests/tests/media/src/android/media/cts/NativeDecoderTest.java
index deb561b..c1c672d 100644
--- a/tests/tests/media/src/android/media/cts/NativeDecoderTest.java
+++ b/tests/tests/media/src/android/media/cts/NativeDecoderTest.java
@@ -419,10 +419,44 @@
     private static native boolean testPlaybackNative(Surface surface,
             int fd, long startOffset, long length);
 
-    public void testMuxer() throws Exception {
+    public void testMuxerAvc() throws Exception {
         testMuxer(R.raw.video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz, false);
     }
 
+    public void testMuxerH263() throws Exception {
+        testMuxer(R.raw.video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz, false);
+    }
+
+    public void testMuxerHevc() throws Exception {
+        testMuxer(R.raw.video_640x360_mp4_hevc_450kbps_30fps_aac_stereo_128kbps_48000hz, false);
+    }
+
+    public void testMuxerVp8() throws Exception {
+        testMuxer(R.raw.video_640x360_webm_vp8_2048kbps_30fps_vorbis_stereo_128kbps_48000hz, true);
+    }
+
+    public void testMuxerVp9() throws Exception {
+        testMuxer(
+                R.raw.video_1280x720_webm_vp9_csd_309kbps_25fps_vorbis_stereo_128kbps_48000hz,
+                true);
+    }
+
+    public void testMuxerVp9NoCsd() throws Exception {
+        testMuxer(R.raw.video_640x360_webm_vp9_1600kbps_30fps_vorbis_stereo_128kbps_48000hz, true);
+    }
+
+    public void testMuxerVp9Hdr() throws Exception {
+        testMuxer(R.raw.video_256x144_webm_vp9_hdr_83kbps_24fps, true);
+    }
+
+    public void testMuxerMpeg2() throws Exception {
+        testMuxer(R.raw.video_176x144_mp4_mpeg2_105kbps_25fps_aac_stereo_128kbps_44100hz, false);
+    }
+
+    public void testMuxerMpeg4() throws Exception {
+        testMuxer(R.raw.video_176x144_mp4_mpeg4_300kbps_25fps_aac_stereo_128kbps_44100hz, false);
+    }
+
     private void testMuxer(int res, boolean webm) throws Exception {
         if (!MediaUtils.checkCodecsForResource(mContext, res)) {
             return; // skip
@@ -450,12 +484,15 @@
         remux.setDataSource(out.getFileDescriptor());
 
         assertEquals("mismatched numer of tracks", org.getTrackCount(), remux.getTrackCount());
-        for (int i = 0; i < 2; i++) {
+        // allow duration mismatch for webm files as ffmpeg does not consider the duration of the
+        // last frame while libwebm (and our framework) does.
+        final long maxDurationDiffUs = webm ? 50000 : 0; // 50ms for webm
+        for (int i = 0; i < org.getTrackCount(); i++) {
             MediaFormat format1 = org.getTrackFormat(i);
             MediaFormat format2 = remux.getTrackFormat(i);
             Log.i("@@@", "org: " + format1);
             Log.i("@@@", "remux: " + format2);
-            assertTrue("different formats", compareFormats(format1, format2));
+            assertTrue("different formats", compareFormats(format1, format2, maxDurationDiffUs));
         }
 
         org.release();
@@ -463,13 +500,71 @@
 
         MediaPlayer player1 = MediaPlayer.create(mContext, res);
         MediaPlayer player2 = MediaPlayer.create(mContext, Uri.parse("file://" + tmpFile));
-        assertEquals("duration is different", player1.getDuration(), player2.getDuration());
+        assertEquals("duration is different",
+                     player1.getDuration(), player2.getDuration(), maxDurationDiffUs * 0.001);
         player1.release();
         player2.release();
         new File(tmpFile).delete();
     }
 
-    boolean compareFormats(MediaFormat f1, MediaFormat f2) {
+    private String hexString(ByteBuffer buf) {
+        if (buf == null) {
+            return "(null)";
+        }
+        final char digits[] =
+            { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+
+        StringBuilder hex = new StringBuilder();
+        for (int i = buf.position(); i < buf.limit(); ++i) {
+            byte c = buf.get(i);
+            hex.append(digits[(c >> 4) & 0xf]);
+            hex.append(digits[c & 0xf]);
+        }
+        return hex.toString();
+    }
+
+    /** returns: null if key is in neither formats, true if they match and false otherwise */
+    private Boolean compareByteBufferInFormats(MediaFormat f1, MediaFormat f2, String key) {
+        ByteBuffer bufF1 = f1.containsKey(key) ? f1.getByteBuffer(key) : null;
+        ByteBuffer bufF2 = f2.containsKey(key) ? f2.getByteBuffer(key) : null;
+        if (bufF1 == null && bufF2 == null) {
+            return null;
+        }
+        if (bufF1 == null || !bufF1.equals(bufF2)) {
+            Log.i("@@@", "org " + key + ": " + hexString(bufF1));
+            Log.i("@@@", "rmx " + key + ": " + hexString(bufF2));
+            return false;
+        }
+        return true;
+    }
+
+    private boolean compareFormats(MediaFormat f1, MediaFormat f2, long maxDurationDiffUs) {
+        final String KEY_DURATION = MediaFormat.KEY_DURATION;
+
+        // allow some difference in durations
+        if (maxDurationDiffUs > 0
+                && f1.containsKey(KEY_DURATION) && f2.containsKey(KEY_DURATION)
+                && Math.abs(f1.getLong(KEY_DURATION)
+                        - f2.getLong(KEY_DURATION)) <= maxDurationDiffUs) {
+            f2.setLong(KEY_DURATION, f1.getLong(KEY_DURATION));
+        }
+
+        // verify hdr-static-info
+        if (Boolean.FALSE.equals(compareByteBufferInFormats(f1, f2, "hdr-static-info"))) {
+            return false;
+        }
+
+        // verify CSDs
+        for (int i = 0;; ++i) {
+            String key = "csd-" + i;
+            Boolean match = compareByteBufferInFormats(f1, f2, key);
+            if (match == null) {
+                break;
+            } else if (match == false) {
+                return false;
+            }
+        }
+
         // there's no good way to compare two MediaFormats, so compare their string
         // representation
         return f1.toString().equals(f2.toString());
diff --git a/tests/tests/os/Android.mk b/tests/tests/os/Android.mk
index bbee284..5397fc6a 100644
--- a/tests/tests/os/Android.mk
+++ b/tests/tests/os/Android.mk
@@ -25,7 +25,7 @@
 LOCAL_MULTILIB := both
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
-    ctsdeviceutil ctstestrunner guava platform-test-annotations
+    ctsdeviceutil ctstestrunner guava
 
 LOCAL_JNI_SHARED_LIBRARIES := libcts_jni libctsos_jni libnativehelper_compat_libc++
 
diff --git a/tests/tests/permission/Android.mk b/tests/tests/permission/Android.mk
index c37ddfc..af0f8c4 100644
--- a/tests/tests/permission/Android.mk
+++ b/tests/tests/permission/Android.mk
@@ -30,7 +30,7 @@
 LOCAL_JAVA_LIBRARIES := telephony-common
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
-    ctstestrunner guava android-ex-camera2 platform-test-annotations
+    ctstestrunner guava android-ex-camera2
 
 LOCAL_JNI_SHARED_LIBRARIES := libctspermission_jni libnativehelper_compat_libc++
 
diff --git a/tests/tests/provider/Android.mk b/tests/tests/provider/Android.mk
index 59fb9f6..30d3e71 100644
--- a/tests/tests/provider/Android.mk
+++ b/tests/tests/provider/Android.mk
@@ -33,7 +33,7 @@
 LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
-    ctsdeviceutil ctstestrunner platform-test-annotations
+    ctsdeviceutil ctstestrunner
 
 LOCAL_JNI_SHARED_LIBRARIES := libcts_jni libnativehelper_compat_libc++
 
diff --git a/tests/tests/security/src/android/security/cts/HwRngTest.java b/tests/tests/security/src/android/security/cts/HwRngTest.java
index 7654b6f..e654f9d 100644
--- a/tests/tests/security/src/android/security/cts/HwRngTest.java
+++ b/tests/tests/security/src/android/security/cts/HwRngTest.java
@@ -45,20 +45,22 @@
     private static final String HWRNG_DRIVER_NAME = "hwrng";
     private static final int HWRNG_DRIVER_MAJOR = 10;
     private static final int HWRNG_DRIVER_MINOR = 183;
+    private static final String REPORT_LOG_NAME = "CtsSecurityTestCases";
 
     /**
      * Reports whether the {@code /dev/hw_random} device is found. This test always passes.
      */
     public void testDeviceFilePresent() {
-        DeviceReportLog report = new DeviceReportLog();
+        String streamName = "test_device_file_present";
+        DeviceReportLog report = new DeviceReportLog(REPORT_LOG_NAME, streamName);
         // Need to report at least one value, otherwise summary won't be logged.
         report.addValue(
-                DEV_HW_RANDOM + " found",
+                "hw_random_found" /* key cannot contain special chars */,
                 DEV_HW_RANDOM.exists() ? 1 : 0,
                 ResultType.WARNING,
                 ResultUnit.NONE);
         report.setSummary(
-                "Hardware RNG exposed",
+                "hardware_rng_exposed",
                 DEV_HW_RANDOM.exists() ? 1 : 0,
                 ResultType.WARNING,
                 ResultUnit.NONE);
diff --git a/tests/tests/theme/Android.mk b/tests/tests/theme/Android.mk
index 8b69a09..0f370d0 100644
--- a/tests/tests/theme/Android.mk
+++ b/tests/tests/theme/Android.mk
@@ -24,8 +24,7 @@
 # When built, explicitly put it in the data partition.
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    ctstestrunner platform-test-annotations
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/tests/view/Android.mk b/tests/tests/view/Android.mk
index 57dc0ce..6a8d49c 100644
--- a/tests/tests/view/Android.mk
+++ b/tests/tests/view/Android.mk
@@ -29,7 +29,7 @@
 LOCAL_JAVA_LIBRARIES := android.test.runner
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
-    ctsdeviceutil ctstestrunner mockito-target platform-test-annotations
+    ctsdeviceutil ctstestrunner mockito-target
 
 LOCAL_JNI_SHARED_LIBRARIES := libctsview_jni libnativehelper_compat_libc++
 
diff --git a/tests/tests/webkit/Android.mk b/tests/tests/webkit/Android.mk
index 3a09f77..d133d80 100644
--- a/tests/tests/webkit/Android.mk
+++ b/tests/tests/webkit/Android.mk
@@ -27,8 +27,7 @@
     ctsdeviceutil \
     ctsdeviceutillegacy \
     ctstestserver \
-    ctstestrunner \
-    platform-test-annotations
+    ctstestrunner
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/tests/widget/Android.mk b/tests/tests/widget/Android.mk
index 96f10fc..4bc9eee 100644
--- a/tests/tests/widget/Android.mk
+++ b/tests/tests/widget/Android.mk
@@ -25,8 +25,7 @@
     mockito-target \
     android-common \
     ctsdeviceutil \
-    ctstestrunner \
-    platform-test-annotations
+    ctstestrunner
 
 LOCAL_JAVA_LIBRARIES := android.test.runner
 
diff --git a/tests/tests/widget/src/android/widget/cts/TextViewTest.java b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
index 35b7851..9f611ad 100644
--- a/tests/tests/widget/src/android/widget/cts/TextViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
@@ -4238,6 +4238,12 @@
 
         // Tap the view to show InsertPointController.
         TouchUtils.tapView(this, mTextView);
+        // bad workaround for waiting onStartInputView of LeanbackIme.apk done
+        try {
+            Thread.sleep(1000);
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
 
         // Execute SelectAll context menu.
         mActivity.runOnUiThread(new Runnable() {
@@ -4249,6 +4255,7 @@
 
         // The selection must be whole of the text contents.
         assertEquals(0, mTextView.getSelectionStart());
+        assertEquals("Hello, World.", mTextView.getText().toString());
         assertEquals(mTextView.length(), mTextView.getSelectionEnd());
     }
 
diff --git a/tests/tvprovider/src/android/tvprovider/cts/TvProviderPerfTest.java b/tests/tvprovider/src/android/tvprovider/cts/TvProviderPerfTest.java
index b5fa019..f1e8ae5 100644
--- a/tests/tvprovider/src/android/tvprovider/cts/TvProviderPerfTest.java
+++ b/tests/tvprovider/src/android/tvprovider/cts/TvProviderPerfTest.java
@@ -51,6 +51,7 @@
 public class TvProviderPerfTest extends CtsAndroidTestCase {
     private static final int TRANSACTION_RUNS = 100;
     private static final int QUERY_RUNS = 10;
+    private static final String REPORT_LOG_NAME = "CtsTvProviderTestCases";
 
     private ContentResolver mContentResolver;
     private String mInputId;
@@ -106,9 +107,10 @@
                 }
             }
         });
-        DeviceReportLog report = new DeviceReportLog();
-        report.addValues("Elapsed time for insert: ",
-                applyBatchTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
+        String streamName = "test_channels";
+        DeviceReportLog report = new DeviceReportLog(REPORT_LOG_NAME, streamName);
+        report.addValues("elapsed_time_for_insert", applyBatchTimes, ResultType.LOWER_BETTER,
+                ResultUnit.MS);
         averages[0] = Stat.getAverage(applyBatchTimes);
 
         // Update
@@ -135,8 +137,8 @@
                 }
             });
         }
-        report.addValues("Elapsed time for update: ",
-                applyBatchTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
+        report.addValues("elapsed_time_for_update", applyBatchTimes, ResultType.LOWER_BETTER,
+                ResultUnit.MS);
         averages[1] = Stat.getAverage(applyBatchTimes);
 
         // Query channels
@@ -151,8 +153,8 @@
                 }
             }
         });
-        report.addValues("Elapsed time for query (channels): ",
-                applyBatchTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
+        report.addValues("elapsed_time_for_query_channels", applyBatchTimes,
+                ResultType.LOWER_BETTER, ResultUnit.MS);
         averages[2] = Stat.getAverage(applyBatchTimes);
 
         // Query a channel
@@ -171,8 +173,8 @@
                 }
             });
         }
-        report.addValues("Elapsed time for query (a channel): ",
-                applyBatchTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
+        report.addValues("elapsed_time_for_query_a_channel", applyBatchTimes,
+                ResultType.LOWER_BETTER, ResultUnit.MS);
         averages[3] = Stat.getAverage(applyBatchTimes);
 
         // Delete
@@ -182,13 +184,12 @@
                 mContentResolver.delete(TvContract.buildChannelsUriForInput(mInputId), null, null);
             }
         });
-        report.addValues("Elapsed time for delete: ",
-                applyBatchTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
+        report.addValues("elapsed_time_for_delete", applyBatchTimes, ResultType.LOWER_BETTER,
+                ResultUnit.MS);
         averages[4] = Stat.getAverage(applyBatchTimes);
 
-        report.addValues("Average elapsed time for insert, update, query (channels), "
-                + "query (a channel), delete: ",
-                averages, ResultType.LOWER_BETTER, ResultUnit.MS);
+        report.addValues("average_elapsed_time_for_insert_update_query_channels_query_a_channel_"
+                + "delete", averages, ResultType.LOWER_BETTER, ResultUnit.MS);
         report.submit(getInstrumentation());
     }
 
@@ -245,9 +246,10 @@
                 }
             }
         });
-        DeviceReportLog report = new DeviceReportLog();
-        report.addValues("Elapsed time for insert: ",
-                applyBatchTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
+        String streamName = "test_programs";
+        DeviceReportLog report = new DeviceReportLog(REPORT_LOG_NAME, streamName);
+        report.addValues("elapsed_time_for_insert", applyBatchTimes, ResultType.LOWER_BETTER,
+                ResultUnit.MS);
         averages[0] = Stat.getAverage(applyBatchTimes);
 
         // Update
@@ -281,8 +283,8 @@
                 }
             }
         });
-        report.addValues("Elapsed time for update: ",
-                applyBatchTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
+        report.addValues("elapsed_time_for_update", applyBatchTimes, ResultType.LOWER_BETTER,
+                ResultUnit.MS);
         averages[1] = Stat.getAverage(applyBatchTimes);
 
         // Query programs
@@ -297,8 +299,8 @@
                 }
             }
         });
-        report.addValues("Elapsed time for query (programs): ",
-                applyBatchTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
+        report.addValues("elapsed_time_for_query_programs", applyBatchTimes,
+                ResultType.LOWER_BETTER, ResultUnit.MS);
         averages[2] = Stat.getAverage(applyBatchTimes);
 
         // Query programs with selection
@@ -317,8 +319,8 @@
                 }
             }
         });
-        report.addValues("Elapsed time for query (programs with selection): ",
-                applyBatchTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
+        report.addValues("elapsed_time_for_query_programs_with_selection", applyBatchTimes,
+                ResultType.LOWER_BETTER, ResultUnit.MS);
         averages[3] = Stat.getAverage(applyBatchTimes);
 
         // Query a program
@@ -337,8 +339,8 @@
                 }
             });
         }
-        report.addValues("Elapsed time for query (a program): ",
-                applyBatchTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
+        report.addValues("elapsed_time_for_query_a_program", applyBatchTimes,
+                ResultType.LOWER_BETTER, ResultUnit.MS);
         averages[4] = Stat.getAverage(applyBatchTimes);
 
         // Delete programs
@@ -354,8 +356,8 @@
                         null, null);
             }
         });
-        report.addValues("Elapsed time for delete programs: ",
-                applyBatchTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
+        report.addValues("elapsed_time_for_delete_programs", applyBatchTimes,
+                ResultType.LOWER_BETTER, ResultUnit.MS);
         averages[5] = Stat.getAverage(applyBatchTimes);
 
         // Delete channels
@@ -366,14 +368,12 @@
                 mContentResolver.delete(channelUri, null, null);
             }
         });
-        report.addValues("Elapsed time for delete channels: ",
-                applyBatchTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
+        report.addValues("elapsed_time_for_delete_channels", applyBatchTimes,
+                ResultType.LOWER_BETTER, ResultUnit.MS);
         averages[6] = Stat.getAverage(applyBatchTimes);
 
-        report.addValues("Average elapsed time for insert, update, query (programs), "
-                + "query (programs with selection), query (a channel), delete (channels), "
-                + "delete (programs): ",
-                averages, ResultType.LOWER_BETTER, ResultUnit.MS);
+        report.addValues("average_elapsed_time_for_all_operations", averages,
+                ResultType.LOWER_BETTER, ResultUnit.MS);
         report.submit(getInstrumentation());
     }
 }
diff --git a/tests/ui/src/android/ui/cts/ScrollingTest.java b/tests/ui/src/android/ui/cts/ScrollingTest.java
index 4902807..da2f334 100644
--- a/tests/ui/src/android/ui/cts/ScrollingTest.java
+++ b/tests/ui/src/android/ui/cts/ScrollingTest.java
@@ -28,6 +28,9 @@
 import java.io.IOException;
 
 public class ScrollingTest extends ActivityInstrumentationTestCase2<ScrollingActivity> {
+
+    private static final String REPORT_LOG_NAME = "CtsUiDeviceTestCases";
+
     private ScrollingActivity mActivity;
 
     public ScrollingTest() {
@@ -69,10 +72,12 @@
                 assertTrue(activity.scrollToTop());
             }
         });
-        DeviceReportLog report = new DeviceReportLog();
-        report.addValues("scrolling time", results, ResultType.LOWER_BETTER,ResultUnit.MS);
+        String streamName = "test_full_scrolling";
+        DeviceReportLog report = new DeviceReportLog(REPORT_LOG_NAME, streamName);
+        report.addValues("scrolling_time", results, ResultType.LOWER_BETTER,ResultUnit.MS);
         Stat.StatResult stat = Stat.getStat(results);
-        report.setSummary("scrolling time", stat.mAverage, ResultType.LOWER_BETTER,ResultUnit.MS);
+        report.setSummary("scrolling_time_average", stat.mAverage,
+                ResultType.LOWER_BETTER,ResultUnit.MS);
         report.submit(getInstrumentation());
     }
 }
diff --git a/tools/cts-tradefed/res/config/cts.xml b/tools/cts-tradefed/res/config/cts.xml
index ee94f6a..2eb0c3e 100644
--- a/tools/cts-tradefed/res/config/cts.xml
+++ b/tools/cts-tradefed/res/config/cts.xml
@@ -23,10 +23,6 @@
 
     <option name="enable-root" value="false" />
 
-    <option name="compatibility:test-arg" value="com.android.tradefed.testtype.AndroidJUnitTest:rerun-from-file:true" />
-    <option name="compatibility:test-arg" value="com.android.tradefed.testtype.AndroidJUnitTest:rerun-from-file-attempts:1" />
-    <option name="compatibility:test-arg" value="com.android.tradefed.testtype.AndroidJUnitTest:fallback-to-serial-rerun:false" />
-
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.PropertyCheck">
         <option name="property-name" value="ro.build.type" />
         <option name="expected-value" value="user"/> <!-- Device should have user build -->
diff --git a/tools/cts-tradefed/tests/src/com/android/compatibility/tradefed/CtsTradefedTest.java b/tools/cts-tradefed/tests/src/com/android/compatibility/tradefed/CtsTradefedTest.java
index 651ee39..47e4837 100644
--- a/tools/cts-tradefed/tests/src/com/android/compatibility/tradefed/CtsTradefedTest.java
+++ b/tools/cts-tradefed/tests/src/com/android/compatibility/tradefed/CtsTradefedTest.java
@@ -34,6 +34,7 @@
     private static final String SUITE_NAME = "CTS";
     private static final String SUITE_PLAN = "cts";
     private static final String DYNAMIC_CONFIG_URL = "";
+    private static final long START_TIME = 123456L;
 
     public void testSuiteInfoLoad() throws Exception {
         // Test the values in the manifest can be loaded
@@ -46,7 +47,7 @@
         CompatibilityBuildProvider provider = new CompatibilityBuildProvider();
         IBuildInfo info = provider.getBuild();
         CompatibilityBuildHelper helper = new CompatibilityBuildHelper(info);
-        helper.init(SUITE_PLAN, DYNAMIC_CONFIG_URL);
+        helper.init(SUITE_PLAN, DYNAMIC_CONFIG_URL, START_TIME);
         assertEquals("Incorrect suite full name", SUITE_FULL_NAME, helper.getSuiteFullName());
         assertEquals("Incorrect suite name", SUITE_NAME, helper.getSuiteName());
         FileUtil.recursiveDelete(root);