am 0a0d79ca: am f6ad3a23: Only run the test if the device has audio output.

* commit '0a0d79ca4d312324562bebda1b3fa0df93b0ec26':
diff --git a/CtsTestCaseList.mk b/CtsTestCaseList.mk
index b3b826d..5b434cc 100644
--- a/CtsTestCaseList.mk
+++ b/CtsTestCaseList.mk
@@ -70,9 +70,12 @@
     CtsDeviceUi \
     CtsIntentReceiverApp \
     CtsIntentSenderApp \
+    CtsLauncherAppsTests \
+    CtsLauncherAppsTestsSupport \
     CtsManagedProfileApp \
     CtsMonkeyApp \
     CtsMonkeyApp2 \
+    CtsSimpleApp \
     CtsSomeAccessibilityServices \
     CtsThemeDeviceApp \
     TestDeviceSetup \
@@ -171,6 +174,7 @@
     CtsAdbTests \
     CtsAppSecurityTests \
     CtsDevicePolicyManagerTestCases \
+    CtsDumpsysHostTestCases \
     CtsHostJank \
     CtsHostUi \
     CtsJdwpSecurityHostTestCases \
diff --git a/apps/CameraITS/CameraITS.pdf b/apps/CameraITS/CameraITS.pdf
index 5e5fd29..2430420 100644
--- a/apps/CameraITS/CameraITS.pdf
+++ b/apps/CameraITS/CameraITS.pdf
Binary files differ
diff --git a/apps/CameraITS/build/envsetup.sh b/apps/CameraITS/build/envsetup.sh
index a95c445..6069341 100644
--- a/apps/CameraITS/build/envsetup.sh
+++ b/apps/CameraITS/build/envsetup.sh
@@ -29,7 +29,7 @@
 python -V 2>&1 | grep -q "Python 2.7" || \
     echo ">> Require python 2.7" >&2
 
-for M in numpy PIL Image matplotlib pylab
+for M in numpy PIL Image matplotlib pylab cv2 scipy.stats scipy.spatial
 do
     python -c "import $M" >/dev/null 2>&1 || \
         echo ">> Require Python $M module" >&2
diff --git a/apps/CameraITS/pymodules/its/device.py b/apps/CameraITS/pymodules/its/device.py
index 6f42051..035e70b 100644
--- a/apps/CameraITS/pymodules/its/device.py
+++ b/apps/CameraITS/pymodules/its/device.py
@@ -53,7 +53,9 @@
     PACKAGE = 'com.android.cts.verifier.camera.its'
     INTENT_START = 'com.android.cts.verifier.camera.its.START'
     ACTION_ITS_RESULT = 'com.android.cts.verifier.camera.its.ACTION_ITS_RESULT'
+    EXTRA_CAMERA_ID = 'camera.its.extra.CAMERA_ID'
     EXTRA_SUCCESS = 'camera.its.extra.SUCCESS'
+    EXTRA_SUMMARY = 'camera.its.extra.SUMMARY'
 
     # TODO: Handle multiple connected devices.
     ADB = "adb -d"
@@ -241,6 +243,20 @@
             raise its.error.Error('Invalid command response')
         return data['objValue']
 
+    def get_camera_ids(self):
+        """Get a list of camera device Ids that can be opened.
+
+        Returns:
+            a list of camera ID string
+        """
+        cmd = {}
+        cmd["cmdName"] = "getCameraIds"
+        self.sock.send(json.dumps(cmd) + "\n")
+        data,_ = self.__read_response_from_socket()
+        if data['tag'] != 'cameraIds':
+            raise its.error.Error('Invalid command response')
+        return data['objValue']['cameraIdArray']
+
     def get_camera_properties(self):
         """Get the camera properties object for the device.
 
@@ -260,7 +276,8 @@
                     regions_af=[[0,0,1,1,1]],
                     do_ae=True, do_awb=True, do_af=True,
                     lock_ae=False, lock_awb=False,
-                    get_results=False):
+                    get_results=False,
+                    ev_comp=0):
         """Perform a 3A operation on the device.
 
         Triggers some or all of AE, AWB, and AF, and returns once they have
@@ -278,6 +295,7 @@
             lock_ae: Request AE lock after convergence, and wait for it.
             lock_awb: Request AWB lock after convergence, and wait for it.
             get_results: Return the 3A results from this function.
+            ev_comp: An EV compensation value to use when running AE.
 
         Region format in args:
             Arguments are lists of weighted regions; each weighted region is a
@@ -307,6 +325,8 @@
             cmd["aeLock"] = True
         if lock_awb:
             cmd["awbLock"] = True
+        if ev_comp != 0:
+            cmd["evComp"] = ev_comp
         self.sock.send(json.dumps(cmd) + "\n")
 
         # Wait for each specified 3A to converge.
@@ -506,21 +526,32 @@
             rets.append(objs if ncap>1 else objs[0])
         return rets if len(rets)>1 else rets[0]
 
-def report_result(camera_id, success):
+def report_result(camera_id, success, summary_path=None):
     """Send a pass/fail result to the device, via an intent.
 
     Args:
         camera_id: The ID string of the camera for which to report pass/fail.
         success: Boolean, indicating if the result was pass or fail.
+        summary_path: (Optional) path to ITS summary file on host PC
 
     Returns:
         Nothing.
     """
-    resultstr = "%s=%s" % (camera_id, 'True' if success else 'False')
-    _run(('%s shell am broadcast '
-          '-a %s --es %s %s') % (ItsSession.ADB, ItsSession.ACTION_ITS_RESULT,
-          ItsSession.EXTRA_SUCCESS, resultstr))
-
+    device_summary_path = "/sdcard/camera_" + camera_id + "_its_summary.txt"
+    if summary_path is not None:
+        _run("%s push %s %s" % (
+                ItsSession.ADB, summary_path, device_summary_path))
+        _run("%s shell am broadcast -a %s --es %s %s --es %s %s --es %s %s" % (
+                ItsSession.ADB, ItsSession.ACTION_ITS_RESULT,
+                ItsSession.EXTRA_CAMERA_ID, camera_id,
+                ItsSession.EXTRA_SUCCESS, 'True' if success else 'False',
+                ItsSession.EXTRA_SUMMARY, device_summary_path))
+    else:
+        _run("%s shell am broadcast -a %s --es %s %s --es %s %s --es %s %s" % (
+                ItsSession.ADB, ItsSession.ACTION_ITS_RESULT,
+                ItsSession.EXTRA_CAMERA_ID, camera_id,
+                ItsSession.EXTRA_SUCCESS, 'True' if success else 'False',
+                ItsSession.EXTRA_SUMMARY, "null"))
 
 def _run(cmd):
     """Replacement for os.system, with hiding of stdout+stderr messages.
diff --git a/apps/CameraITS/pymodules/its/image.py b/apps/CameraITS/pymodules/its/image.py
index f2425e1..b3bdb65 100644
--- a/apps/CameraITS/pymodules/its/image.py
+++ b/apps/CameraITS/pymodules/its/image.py
@@ -540,9 +540,13 @@
     img = numpy.vstack(chs).T.reshape(h/f,w/f,chans)
     return img
 
-def __measure_color_checker_patch(img, xc,yc, patch_size):
+def __get_color_checker_patch(img, xc,yc, patch_size):
     r = patch_size/2
-    tile = img[yc-r:yc+r+1:, xc-r:xc+r+1:, ::]
+    tile = img[yc-r:yc+r:, xc-r:xc+r:, ::]
+    return tile
+
+def __measure_color_checker_patch(img, xc,yc, patch_size):
+    tile = __get_color_checker_patch(img, xc,yc, patch_size)
     means = tile.mean(1).mean(0)
     return means
 
@@ -561,15 +565,9 @@
     * Standard color checker chart with standard-sized black borders.
 
     The values returned are in the coordinate system of the chart; that is,
-    the "origin" patch is the brown patch that is in the chart's top-left
-    corner when it is in the normal upright/horizontal orientation. (The chart
-    may be any of the four main orientations in the image.)
-
-    The chart is 6x4 patches in the normal upright orientation. The return
-    values of this function are the center coordinate of the top-left patch,
-    and the displacement vectors to the next patches to the right and below
-    the top-left patch. From these pieces of data, the center coordinates of
-    any of the patches can be computed.
+    patch (0,0) is the brown patch that is in the chart's top-left corner when
+    it is in the normal upright/horizontal orientation. (The chart may be any
+    of the four main orientations in the image.)
 
     Args:
         img: Input image, as a numpy array with pixels in [0,1].
@@ -677,6 +675,7 @@
             patches[yi].append((xc,yc))
 
     # Sanity check: test that the R,G,B,black,white patches are correct.
+    sanity_failed = False
     patch_info = [(2,2,[0]), # Red
                   (2,1,[1]), # Green
                   (2,0,[2]), # Blue
@@ -689,16 +688,19 @@
         means = __measure_color_checker_patch(img, xc,yc, 64)
         if (min([means[i] for i in high_chans]+[1]) < \
                 max([means[i] for i in low_chans]+[0])):
-            print "Color patch sanity check failed: patch", i
-            # If the debug info is requested, then don't assert that the patches
-            # are matched, to allow the caller to see the output.
-            if debug_fname_prefix is None:
-                assert(0)
+            sanity_failed = True
 
     if debug_fname_prefix is not None:
-        for (xc,yc) in sum(patches,[]):
-            img[yc,xc] = 1.0
-        write_image(img, debug_fname_prefix+"_2.jpg")
+        gridimg = numpy.zeros([4*(32+2), 6*(32+2), 3])
+        for yi in range(4):
+            for xi in range(6):
+                xc,yc = patches[yi][xi]
+                tile = __get_color_checker_patch(img, xc,yc, 32)
+                gridimg[yi*(32+2)+1:yi*(32+2)+1+32,
+                        xi*(32+2)+1:xi*(32+2)+1+32, :] = tile
+        write_image(gridimg, debug_fname_prefix+"_2.png")
+
+    assert(not sanity_failed)
 
     return patches
 
diff --git a/apps/CameraITS/pymodules/its/objects.py b/apps/CameraITS/pymodules/its/objects.py
index 809a98a..22540b8 100644
--- a/apps/CameraITS/pymodules/its/objects.py
+++ b/apps/CameraITS/pymodules/its/objects.py
@@ -88,6 +88,7 @@
         its.device.do_capture function.
     """
     req = {
+        "android.control.captureIntent": 6,
         "android.control.mode": 0,
         "android.control.aeMode": 0,
         "android.control.awbMode": 0,
@@ -101,6 +102,7 @@
                 int_to_rational([1,0,0, 0,1,0, 0,0,1]),
         "android.colorCorrection.gains": [1,1,1,1],
         "android.tonemap.mode": 1,
+        "android.shading.mode": 1
         }
     if linear_tonemap:
         req["android.tonemap.mode"] = 0
@@ -121,6 +123,21 @@
         "android.tonemap.mode": 1,
         }
 
+def fastest_auto_capture_request(props):
+    """Return an auto capture request for the fastest capture.
+
+    Args:
+        props: the object returned from its.device.get_camera_properties().
+
+    Returns:
+        A capture request with everything set to auto and all filters that
+            may slow down capture set to OFF or FAST if possible
+    """
+    req = auto_capture_request()
+    turn_slow_filters_off(props, req)
+
+    return req
+
 def get_available_output_sizes(fmt, props):
     """Return a sorted list of available output sizes for a given format.
 
@@ -140,6 +157,51 @@
     out_sizes.sort(reverse=True)
     return out_sizes
 
+def set_filter_off_or_fast_if_possible(props, req, available_modes, filter):
+    """Check and set controlKey to off or fast in req.
+
+    Args:
+        props: the object returned from its.device.get_camera_properties().
+        req: the input request. filter will be set to OFF or FAST if possible.
+        available_modes: the key to check available modes.
+        filter: the filter key
+
+    Returns:
+        Nothing.
+    """
+    if props.has_key(available_modes):
+        if 0 in props[available_modes]:
+            req[filter] = 0
+        elif 1 in props[available_modes]:
+            req[filter] = 1
+
+def turn_slow_filters_off(props, req):
+    """Turn filters that may slow FPS down to OFF or FAST in input request.
+
+    This function modifies the request argument, such that filters that may
+    reduce the frames-per-second throughput of the camera device will be set to
+    OFF or FAST if possible.
+
+    Args:
+        props: the object returned from its.device.get_camera_properties().
+        req: the input request.
+
+    Returns:
+        Nothing.
+    """
+    set_filter_off_or_fast_if_possible(props, req,
+        "android.noiseReduction.availableNoiseReductionModes",
+        "android.noiseReduction.mode")
+    set_filter_off_or_fast_if_possible(props, req,
+        "android.colorCorrection.availableAberrationModes",
+        "android.colorCorrection.aberrationMode")
+    set_filter_off_or_fast_if_possible(props, req,
+        "android.hotPixel.availableHotPixelModes",
+        "android.hotPixel.mode")
+    set_filter_off_or_fast_if_possible(props, req,
+        "android.edge.availableEdgeModes",
+        "android.edge.mode")
+
 def get_fastest_manual_capture_settings(props):
     """Return a capture request and format spec for the fastest capture.
 
@@ -157,6 +219,9 @@
     s = min(props['android.sensor.info.sensitivityRange'])
     e = min(props['android.sensor.info.exposureTimeRange'])
     req = manual_capture_request(s,e)
+
+    turn_slow_filters_off(props, req)
+
     return req, out_spec
 
 def get_max_digital_zoom(props):
diff --git a/apps/CameraITS/tests/dng_noise_model/DngNoiseModel.pdf b/apps/CameraITS/tests/dng_noise_model/DngNoiseModel.pdf
new file mode 100644
index 0000000..01389fa
--- /dev/null
+++ b/apps/CameraITS/tests/dng_noise_model/DngNoiseModel.pdf
Binary files differ
diff --git a/apps/CameraITS/tools/compute_dng_noise_model.py b/apps/CameraITS/tests/dng_noise_model/dng_noise_model.py
similarity index 86%
rename from apps/CameraITS/tools/compute_dng_noise_model.py
rename to apps/CameraITS/tests/dng_noise_model/dng_noise_model.py
index 1b57754..19b6c92 100644
--- a/apps/CameraITS/tools/compute_dng_noise_model.py
+++ b/apps/CameraITS/tests/dng_noise_model/dng_noise_model.py
@@ -50,7 +50,7 @@
         s_e_prod *= 2
 
         # Capture raw frames across the full sensitivity range.
-        NUM_SENS_STEPS = 15
+        NUM_SENS_STEPS = 9
         sens_step = int((sens_max - sens_min - 1) / float(NUM_SENS_STEPS))
         reqs = []
         sens = []
@@ -75,7 +75,7 @@
         patches = [(2*x,2*y) for (x,y) in sum(patches,[])]
 
         lines = []
-        for (s,cap) in zip(sens,caps):
+        for iouter, (s,cap) in enumerate(zip(sens,caps)):
             # For each capture, compute the mean value in each patch, for each
             # Bayer plane; discard patches where pixels are close to clamped.
             # Also compute the variance.
@@ -117,10 +117,17 @@
             #assert(m > 0)
             #assert(b >= 0)
 
-            # Draw a plot.
-            pylab.plot(xs, ys, 'r')
-            pylab.plot([0,xs[-1]],[b,m*xs[-1]+b],'b')
-            matplotlib.pyplot.savefig("%s_plot_mean_vs_variance.png" % (NAME))
+            if iouter == 0:
+                pylab.plot(xs, ys, 'r', label="Measured")
+                pylab.plot([0,xs[-1]],[b,m*xs[-1]+b],'b', label="Fit")
+            else:
+                pylab.plot(xs, ys, 'r')
+                pylab.plot([0,xs[-1]],[b,m*xs[-1]+b],'b')
+
+        pylab.xlabel("Mean")
+        pylab.ylabel("Variance")
+        pylab.legend()
+        matplotlib.pyplot.savefig("%s_plot_mean_vs_variance.png" % (NAME))
 
         # Now fit a line across the (m,b) line parameters for each sensitivity.
         # The gradient (m) params are fit to the "S" line, and the offset (b)
@@ -132,11 +139,16 @@
         mO,bO = numpy.polyfit(gains, Os, 1)
 
         # Plot curve "O" as 10x, so it fits in the same scale as curve "S".
-        pylab.plot(gains, [10*o for o in Os], 'r')
+        fig = matplotlib.pyplot.figure()
+        pylab.plot(gains, [10*o for o in Os], 'r', label="Measured")
         pylab.plot([gains[0],gains[-1]],
-                [10*mO*gains[0]+10*bO, 10*mO*gains[-1]+10*bO], 'b')
-        pylab.plot(gains, Ss, 'r')
-        pylab.plot([gains[0],gains[-1]], [mS*gains[0]+bS, mS*gains[-1]+bS], 'b')
+                [10*mO*gains[0]+10*bO, 10*mO*gains[-1]+10*bO],'r--',label="Fit")
+        pylab.plot(gains, Ss, 'b', label="Measured")
+        pylab.plot([gains[0],gains[-1]], [mS*gains[0]+bS,mS*gains[-1]+bS],'b--',
+                label="Fit")
+        pylab.xlabel("Sensitivity")
+        pylab.ylabel("Model parameter: S (blue), O x10 (red)")
+        pylab.legend()
         matplotlib.pyplot.savefig("%s_plot_S_O.png" % (NAME))
 
         print """
diff --git a/apps/CameraITS/tests/inprog/test_burst_sameness_auto.py b/apps/CameraITS/tests/inprog/test_burst_sameness_auto.py
index fdf72be..87500c7 100644
--- a/apps/CameraITS/tests/inprog/test_burst_sameness_auto.py
+++ b/apps/CameraITS/tests/inprog/test_burst_sameness_auto.py
@@ -48,7 +48,7 @@
         cam.do_3a(lock_ae=True, lock_awb=True)
 
         # After 3A has converged, lock AE+AWB for the duration of the test.
-        req = its.objects.auto_capture_request()
+        req = its.objects.fastest_auto_capture_request(props)
         req["android.blackLevel.lock"] = True
         req["android.control.awbLock"] = True
         req["android.control.aeLock"] = True
diff --git a/apps/CameraITS/tests/inprog/test_burst_sameness_fullres_auto.py b/apps/CameraITS/tests/inprog/test_burst_sameness_fullres_auto.py
index a8d1d45..932c051 100644
--- a/apps/CameraITS/tests/inprog/test_burst_sameness_fullres_auto.py
+++ b/apps/CameraITS/tests/inprog/test_burst_sameness_fullres_auto.py
@@ -47,7 +47,7 @@
         cam.do_3a(lock_ae=True, lock_awb=True)
 
         # After 3A has converged, lock AE+AWB for the duration of the test.
-        req = its.objects.auto_capture_request()
+        req = its.objects.fastest_auto_capture_request(props)
         req["android.blackLevel.lock"] = True
         req["android.control.awbLock"] = True
         req["android.control.aeLock"] = True
diff --git a/apps/CameraITS/tests/inprog/test_ev_compensation.py b/apps/CameraITS/tests/inprog/test_ev_compensation.py
deleted file mode 100644
index f9b0cd3..0000000
--- a/apps/CameraITS/tests/inprog/test_ev_compensation.py
+++ /dev/null
@@ -1,71 +0,0 @@
-# Copyright 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.
-
-import its.image
-import its.device
-import its.objects
-import os.path
-import pylab
-import matplotlib
-import matplotlib.pyplot
-import numpy
-
-def main():
-    """Tests that EV compensation is applied.
-    """
-    NAME = os.path.basename(__file__).split(".")[0]
-
-    MAX_LUMA_DELTA_THRESH = 0.01
-    AVG_LUMA_DELTA_THRESH = 0.001
-
-    with its.device.ItsSession() as cam:
-        props = cam.get_camera_properties()
-        cam.do_3a()
-
-        # Capture auto shots, but with a linear tonemap.
-        req = its.objects.auto_capture_request()
-        req["android.tonemap.mode"] = 0
-        req["android.tonemap.curveRed"] = (0.0, 0.0, 1.0, 1.0)
-        req["android.tonemap.curveGreen"] = (0.0, 0.0, 1.0, 1.0)
-        req["android.tonemap.curveBlue"] = (0.0, 0.0, 1.0, 1.0)
-
-        evs = range(-4,5)
-        lumas = []
-        for ev in evs:
-            req['android.control.aeExposureCompensation'] = ev
-            cap = cam.do_capture(req)
-            y = its.image.convert_capture_to_planes(cap)[0]
-            tile = its.image.get_image_patch(y, 0.45,0.45,0.1,0.1)
-            lumas.append(its.image.compute_image_means(tile)[0])
-
-        ev_step_size_in_stops = its.objects.rational_to_float(
-                props['android.control.aeCompensationStep'])
-        luma_increase_per_step = pow(2, ev_step_size_in_stops)
-        expected_lumas = [lumas[0] * pow(luma_increase_per_step, i) \
-                for i in range(len(evs))]
-
-        pylab.plot(evs, lumas, 'r')
-        pylab.plot(evs, expected_lumas, 'b')
-        matplotlib.pyplot.savefig("%s_plot_means.png" % (NAME))
-
-        luma_diffs = [expected_lumas[i] - lumas[i] for i in range(len(evs))]
-        max_diff = max(luma_diffs)
-        avg_diff = sum(luma_diffs) / len(luma_diffs)
-        print "Max delta between modeled and measured lumas:", max_diff
-        print "Avg delta between modeled and measured lumas:", avg_diff
-        assert(max_diff < MAX_LUMA_DELTA_THRESH)
-        assert(avg_diff < AVG_LUMA_DELTA_THRESH)
-
-if __name__ == '__main__':
-    main()
diff --git a/apps/CameraITS/tests/scene1/test_ae_precapture_trigger.py b/apps/CameraITS/tests/scene1/test_ae_precapture_trigger.py
index b1f51f3..563cebd 100644
--- a/apps/CameraITS/tests/scene1/test_ae_precapture_trigger.py
+++ b/apps/CameraITS/tests/scene1/test_ae_precapture_trigger.py
@@ -17,6 +17,9 @@
 import its.objects
 import its.target
 
+# AE must converge within this number of auto requests under scene1
+THRESH_AE_CONVERGE = 8
+
 def main():
     """Test the AE state machine when using the precapture trigger.
     """
@@ -68,7 +71,7 @@
 
         # Capture some more auto requests, and AE should converge.
         auto_req['android.control.aePrecaptureTrigger'] = 0
-        caps = cam.do_capture([auto_req]*5, fmt)
+        caps = cam.do_capture([auto_req] * THRESH_AE_CONVERGE, fmt)
         state = caps[-1]['metadata']['android.control.aeState']
         print "AE state after auto request:", state
         assert(state == CONVERGED)
diff --git a/apps/CameraITS/tests/scene1/test_capture_result.py b/apps/CameraITS/tests/scene1/test_capture_result.py
index 331d1cd..ec919f8 100644
--- a/apps/CameraITS/tests/scene1/test_capture_result.py
+++ b/apps/CameraITS/tests/scene1/test_capture_result.py
@@ -39,14 +39,15 @@
                              its.caps.per_frame_control(props))
 
         manual_tonemap = [0,0, 1,1] # Linear
-        manual_transform = its.objects.int_to_rational([1,2,3, 4,5,6, 7,8,9])
-        manual_gains = [1,2,3,4]
+        manual_transform = its.objects.float_to_rational(
+                [-1.5,-1.0,-0.5, 0.0,0.5,1.0, 1.5,2.0,3.0])
+        manual_gains = [1,1.5,2.0,3.0]
         manual_region = [{"x":8,"y":8,"width":128,"height":128,"weight":1}]
         manual_exp_time = min(props['android.sensor.info.exposureTimeRange'])
         manual_sensitivity = min(props['android.sensor.info.sensitivityRange'])
 
         # The camera HAL may not support different gains for two G channels.
-        manual_gains_ok = [[1,2,3,4],[1,2,2,4],[1,3,3,4]]
+        manual_gains_ok = [[1,1.5,2.0,3.0],[1,1.5,1.5,3.0],[1,2.0,2.0,3.0]]
 
         auto_req = its.objects.auto_capture_request()
         auto_req["android.statistics.lensShadingMapMode"] = 1
diff --git a/apps/CameraITS/tests/scene1/test_crop_region_raw.py b/apps/CameraITS/tests/scene1/test_crop_region_raw.py
index 9fc52cb..189e987 100644
--- a/apps/CameraITS/tests/scene1/test_crop_region_raw.py
+++ b/apps/CameraITS/tests/scene1/test_crop_region_raw.py
@@ -20,25 +20,6 @@
 import numpy
 import os.path
 
-
-def check_crop_region(expected, reported, active, err_threshold):
-    """Check if the reported region is within the tolerance.
-
-    Args:
-        expected: expected crop region
-        reported: reported crop region
-        active: active resolution
-        err_threshold: error threshold for the active resolution
-    """
-
-    ex = (active["right"] - active["left"]) * err_threshold
-    ey = (active["bottom"] - active["top"]) * err_threshold
-
-    assert ((abs(expected["left"] - reported["left"]) <= ex) and
-            (abs(expected["right"] - reported["right"]) <= ex) and
-            (abs(expected["top"] - reported["top"]) <= ey) and
-            (abs(expected["bottom"] - reported["bottom"]) <= ey))
-
 def main():
     """Test that raw streams are not croppable.
     """
@@ -53,6 +34,7 @@
                              its.caps.raw16(props) and
                              its.caps.per_frame_control(props))
 
+        # Calculate the active sensor region for a full (non-cropped) image.
         a = props['android.sensor.info.activeArraySize']
         ax, ay = a["left"], a["top"]
         aw, ah = a["right"] - a["left"], a["bottom"] - a["top"]
@@ -65,6 +47,19 @@
             "bottom": ah
         }
 
+        # Calculate a center crop region.
+        zoom = min(3.0, its.objects.get_max_digital_zoom(props))
+        assert(zoom >= 1)
+        cropw = aw / zoom
+        croph = ah / zoom
+
+        crop_region = {
+            "left": aw / 2 - cropw / 2,
+            "top": ah / 2 - croph / 2,
+            "right": aw / 2 + cropw / 2,
+            "bottom": ah / 2 + croph / 2
+        }
+
         # Capture without a crop region.
         # Use a manual request with a linear tonemap so that the YUV and RAW
         # should look the same (once converted by the its.image module).
@@ -72,59 +67,57 @@
         req = its.objects.manual_capture_request(s,e, True)
         cap1_raw, cap1_yuv = cam.do_capture(req, cam.CAP_RAW_YUV)
 
-        # Calculate a center crop region.
-        zoom = min(3.0, its.objects.get_max_digital_zoom(props))
-        assert(zoom >= 1)
-        cropw = aw / zoom
-        croph = ah / zoom
-
-        req["android.scaler.cropRegion"] = {
-            "left": aw / 2 - cropw / 2,
-            "top": ah / 2 - croph / 2,
-            "right": aw / 2 + cropw / 2,
-            "bottom": ah / 2 + croph / 2
-        }
-
-        # when both YUV and RAW are requested, the crop region that's
-        # applied to YUV should be reported.
-        crop_region = req["android.scaler.cropRegion"]
-        if crop_region == full_region:
-            crop_region_err_thresh = 0.0
-        else:
-            crop_region_err_thresh = CROP_REGION_ERROR_THRESHOLD
-
+        # Capture with a crop region.
+        req["android.scaler.cropRegion"] = crop_region
         cap2_raw, cap2_yuv = cam.do_capture(req, cam.CAP_RAW_YUV)
 
+        # Check the metadata related to crop regions.
+        # When both YUV and RAW are requested, the crop region that's
+        # applied to YUV should be reported.
+        # Note that the crop region returned by the cropped captures doesn't
+        # need to perfectly match the one that was requested.
         imgs = {}
-        for s, cap, cr, err_delta in [("yuv_full", cap1_yuv, full_region, 0),
-                      ("raw_full", cap1_raw, full_region, 0),
-                      ("yuv_crop", cap2_yuv, crop_region, crop_region_err_thresh),
-                      ("raw_crop", cap2_raw, crop_region, crop_region_err_thresh)]:
+        for s, cap, cr_expected, err_delta in [
+                ("yuv_full",cap1_yuv,full_region,0),
+                ("raw_full",cap1_raw,full_region,0),
+                ("yuv_crop",cap2_yuv,crop_region,CROP_REGION_ERROR_THRESHOLD),
+                ("raw_crop",cap2_raw,crop_region,CROP_REGION_ERROR_THRESHOLD)]:
+
+            # Convert the capture to RGB and dump to a file.
             img = its.image.convert_capture_to_rgb_image(cap, props=props)
             its.image.write_image(img, "%s_%s.jpg" % (NAME, s))
-            r = cap["metadata"]["android.scaler.cropRegion"]
-            x, y = r["left"], r["top"]
-            w, h = r["right"] - r["left"], r["bottom"] - r["top"]
             imgs[s] = img
-            print "Crop on %s: (%d,%d %dx%d)" % (s, x, y, w, h)
-            check_crop_region(cr, r, a, err_delta)
+
+            # Get the crop region that is reported in the capture result.
+            cr_reported = cap["metadata"]["android.scaler.cropRegion"]
+            x, y = cr_reported["left"], cr_reported["top"]
+            w = cr_reported["right"] - cr_reported["left"]
+            h = cr_reported["bottom"] - cr_reported["top"]
+            print "Crop reported on %s: (%d,%d %dx%d)" % (s, x, y, w, h)
+
+            # Test that the reported crop region is the same as the expected
+            # one, for a non-cropped capture, and is close to the expected one,
+            # for a cropped capture.
+            ex = aw * err_delta
+            ey = ah * err_delta
+            assert ((abs(cr_expected["left"] - cr_reported["left"]) <= ex) and
+                    (abs(cr_expected["right"] - cr_reported["right"]) <= ex) and
+                    (abs(cr_expected["top"] - cr_reported["top"]) <= ey) and
+                    (abs(cr_expected["bottom"] - cr_reported["bottom"]) <= ey))
 
         # Also check the image content; 3 of the 4 shots should match.
         # Note that all the shots are RGB below; the variable names correspond
         # to what was captured.
-        # Average the images down 4x4 -> 1 prior to comparison to smooth out
-        # noise.
-        # Shrink the YUV images an additional 2x2 -> 1 to account for the size
-        # reduction that the raw images went through in the RGB conversion.
+
+        # Shrink the YUV images 2x2 -> 1 to account for the size reduction that
+        # the raw images went through in the RGB conversion.
         imgs2 = {}
         for s,img in imgs.iteritems():
             h,w,ch = img.shape
-            m = 4
             if s in ["yuv_full", "yuv_crop"]:
-                m = 8
-            img = img.reshape(h/m,m,w/m,m,3).mean(3).mean(1).reshape(h/m,w/m,3)
+                img = img.reshape(h/2,2,w/2,2,3).mean(3).mean(1)
+                img = img.reshape(h/2,w/2,3)
             imgs2[s] = img
-            print s, img.shape
 
         # Strip any border pixels from the raw shots (since the raw images may
         # be larger than the YUV images). Assume a symmetric padded border.
@@ -139,7 +132,10 @@
         for s,img in imgs2.iteritems():
             its.image.write_image(img, "%s_comp_%s.jpg" % (NAME, s))
 
-        # Compute image diffs.
+        # Compute diffs between images of the same type.
+        # The raw_crop and raw_full shots should be identical (since the crop
+        # doesn't apply to raw images), and the yuv_crop and yuv_full shots
+        # should be different.
         diff_yuv = numpy.fabs((imgs2["yuv_full"] - imgs2["yuv_crop"])).mean()
         diff_raw = numpy.fabs((imgs2["raw_full"] - imgs2["raw_crop"])).mean()
         print "YUV diff (crop vs. non-crop):", diff_yuv
diff --git a/apps/CameraITS/tests/scene1/test_dng_noise_model.py b/apps/CameraITS/tests/scene1/test_dng_noise_model.py
new file mode 100644
index 0000000..51270b6
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/test_dng_noise_model.py
@@ -0,0 +1,114 @@
+# Copyright 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.
+
+import its.device
+import its.caps
+import its.objects
+import its.image
+import os.path
+import pylab
+import matplotlib
+import matplotlib.pyplot
+
+def main():
+    """Verify that the DNG raw model parameters are correct.
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    NUM_STEPS = 4
+
+    # Pass if the difference between expected and computed variances is small,
+    # defined as being within an absolute variance delta of 0.0005, or within
+    # 20% of the expected variance, whichever is larger; this is to allow the
+    # test to pass in the presence of some randomness (since this test is
+    # measuring noise of a small patch) and some imperfect scene conditions
+    # (since ITS doesn't require a perfectly uniformly lit scene).
+    DIFF_THRESH = 0.0005
+    FRAC_THRESH = 0.2
+
+    with its.device.ItsSession() as cam:
+
+        props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.raw(props) and
+                             its.caps.raw16(props) and
+                             its.caps.manual_sensor(props) and
+                             its.caps.read_3a(props) and
+                             its.caps.per_frame_control(props))
+
+        white_level = float(props['android.sensor.info.whiteLevel'])
+        black_levels = props['android.sensor.blackLevelPattern']
+        cfa_idxs = its.image.get_canonical_cfa_order(props)
+        black_levels = [black_levels[i] for i in cfa_idxs]
+
+        # Expose for the scene with min sensitivity
+        sens_min, sens_max = props['android.sensor.info.sensitivityRange']
+        sens_step = (sens_max - sens_min) / NUM_STEPS
+        s_ae,e_ae,_,_,_  = cam.do_3a(get_results=True)
+        s_e_prod = s_ae * e_ae
+        sensitivities = range(sens_min, sens_max, sens_step)
+
+        var_expected = [[],[],[],[]]
+        var_measured = [[],[],[],[]]
+        for sens in sensitivities:
+
+            # Capture a raw frame with the desired sensitivity.
+            exp = int(s_e_prod / float(sens))
+            req = its.objects.manual_capture_request(sens, exp)
+            cap = cam.do_capture(req, cam.CAP_RAW)
+
+            # Test each raw color channel (R, GR, GB, B):
+            noise_profile = cap["metadata"]["android.sensor.noiseProfile"]
+            assert((len(noise_profile)) == 4)
+            for ch in range(4):
+                # Get the noise model parameters for this channel of this shot.
+                s,o = noise_profile[cfa_idxs[ch]]
+
+                # Get a center tile of the raw channel, and compute the mean.
+                # Use a very small patch to ensure gross uniformity (i.e. so
+                # non-uniform lighting or vignetting doesn't affect the variance
+                # calculation).
+                plane = its.image.convert_capture_to_planes(cap, props)[ch]
+                plane = (plane * white_level - black_levels[ch]) / (
+                        white_level - black_levels[ch])
+                tile = its.image.get_image_patch(plane, 0.49,0.49,0.02,0.02)
+                mean = tile.mean()
+
+                # Calculate the expected variance based on the model, and the
+                # measured variance from the tile.
+                var_measured[ch].append(
+                        its.image.compute_image_variances(tile)[0])
+                var_expected[ch].append(s * mean + o)
+
+    for ch in range(4):
+        pylab.plot(sensitivities, var_expected[ch], "rgkb"[ch],
+                label=["R","GR","GB","B"][ch]+" expected")
+        pylab.plot(sensitivities, var_measured[ch], "rgkb"[ch]+"--",
+                label=["R", "GR", "GB", "B"][ch]+" measured")
+    pylab.xlabel("Sensitivity")
+    pylab.ylabel("Center patch variance")
+    pylab.legend(loc=2)
+    matplotlib.pyplot.savefig("%s_plot.png" % (NAME))
+
+    # Pass/fail check.
+    for ch in range(4):
+        diffs = [var_measured[ch][i] - var_expected[ch][i]
+                 for i in range(NUM_STEPS)]
+        print "Diffs (%s):"%(["R","GR","GB","B"][ch]), diffs
+        for i,diff in enumerate(diffs):
+            thresh = max(DIFF_THRESH, FRAC_THRESH * var_expected[ch][i])
+            assert(diff <= thresh)
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/scene1/test_ev_compensation_advanced.py b/apps/CameraITS/tests/scene1/test_ev_compensation_advanced.py
new file mode 100644
index 0000000..6341c67
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/test_ev_compensation_advanced.py
@@ -0,0 +1,83 @@
+# Copyright 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.
+
+import its.image
+import its.device
+import its.caps
+import its.objects
+import os.path
+import pylab
+import matplotlib
+import matplotlib.pyplot
+import numpy
+
+def main():
+    """Tests that EV compensation is applied.
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    MAX_LUMA_DELTA_THRESH = 0.02
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.manual_sensor(props) and
+                             its.caps.manual_post_proc(props) and
+                             its.caps.per_frame_control(props))
+
+        evs = range(-4,5)
+        lumas = []
+        for ev in evs:
+            # Re-converge 3A, and lock AE once converged. skip AF trigger as
+            # dark/bright scene could make AF convergence fail and this test
+            # doesn't care the image sharpness.
+            cam.do_3a(ev_comp=ev, lock_ae=True, do_af=False)
+
+            # Capture a single shot with the same EV comp and locked AE.
+            req = its.objects.auto_capture_request()
+            req['android.control.aeExposureCompensation'] = ev
+            req["android.control.aeLock"] = True
+            # Use linear tone curve to avoid brightness being impacted
+            # by tone curves.
+            req["android.tonemap.mode"] = 0
+            req["android.tonemap.curveRed"] = [0.0,0.0, 1.0,1.0]
+            req["android.tonemap.curveGreen"] = [0.0,0.0, 1.0,1.0]
+            req["android.tonemap.curveBlue"] = [0.0,0.0, 1.0,1.0]
+            cap = cam.do_capture(req)
+            y = its.image.convert_capture_to_planes(cap)[0]
+            tile = its.image.get_image_patch(y, 0.45,0.45,0.1,0.1)
+            lumas.append(its.image.compute_image_means(tile)[0])
+
+        ev_step_size_in_stops = its.objects.rational_to_float(
+                props['android.control.aeCompensationStep'])
+        luma_increase_per_step = pow(2, ev_step_size_in_stops)
+        print "ev_step_size_in_stops", ev_step_size_in_stops
+        imid = len(lumas) / 2
+        expected_lumas = [lumas[imid] / pow(luma_increase_per_step, i)
+                          for i in range(imid , 0, -1)]  + \
+                         [lumas[imid] * pow(luma_increase_per_step, i-imid)
+                          for i in range(imid, len(evs))]
+
+        pylab.plot(evs, lumas, 'r')
+        pylab.plot(evs, expected_lumas, 'b')
+        matplotlib.pyplot.savefig("%s_plot_means.png" % (NAME))
+
+        luma_diffs = [expected_lumas[i] - lumas[i] for i in range(len(evs))]
+        max_diff = max(abs(i) for i in luma_diffs)
+        avg_diff = abs(numpy.array(luma_diffs)).mean()
+        print "Max delta between modeled and measured lumas:", max_diff
+        print "Avg delta between modeled and measured lumas:", avg_diff
+        assert(max_diff < MAX_LUMA_DELTA_THRESH)
+
+if __name__ == '__main__':
+    main()
diff --git a/apps/CameraITS/tests/scene1/test_ev_compensation_basic.py b/apps/CameraITS/tests/scene1/test_ev_compensation_basic.py
new file mode 100644
index 0000000..13f318f
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/test_ev_compensation_basic.py
@@ -0,0 +1,60 @@
+# Copyright 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.
+
+import its.image
+import its.device
+import its.objects
+import os.path
+import pylab
+import matplotlib
+import matplotlib.pyplot
+import numpy
+
+def main():
+    """Tests that EV compensation is applied.
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+
+        evs = range(-4,5)
+        lumas = []
+        for ev in evs:
+            # Re-converge 3A, and lock AE once converged. skip AF trigger as
+            # dark/bright scene could make AF convergence fail and this test
+            # doesn't care the image sharpness.
+            cam.do_3a(ev_comp=ev, lock_ae=True, do_af=False)
+
+            # Capture a single shot with the same EV comp and locked AE.
+            req = its.objects.auto_capture_request()
+            req['android.control.aeExposureCompensation'] = ev
+            req["android.control.aeLock"] = True
+            cap = cam.do_capture(req)
+            y = its.image.convert_capture_to_planes(cap)[0]
+            tile = its.image.get_image_patch(y, 0.45,0.45,0.1,0.1)
+            lumas.append(its.image.compute_image_means(tile)[0])
+
+        pylab.plot(evs, lumas, 'r')
+        matplotlib.pyplot.savefig("%s_plot_means.png" % (NAME))
+
+        luma_diffs = numpy.diff(lumas)
+        min_luma_diffs = min(luma_diffs)
+        print "Min of the luma value difference between adjacent ev comp: ", \
+                min_luma_diffs
+        # All luma brightness should be increasing with increasing ev comp.
+        assert(min_luma_diffs > 0)
+
+if __name__ == '__main__':
+    main()
diff --git a/apps/CameraITS/tests/scene1/test_locked_burst.py b/apps/CameraITS/tests/scene1/test_locked_burst.py
index 5cea30c..90662db 100644
--- a/apps/CameraITS/tests/scene1/test_locked_burst.py
+++ b/apps/CameraITS/tests/scene1/test_locked_burst.py
@@ -41,7 +41,7 @@
         cam.do_3a(do_af=True, lock_ae=True, lock_awb=True)
 
         # After 3A has converged, lock AE+AWB for the duration of the test.
-        req = its.objects.auto_capture_request()
+        req = its.objects.fastest_auto_capture_request(props)
         req["android.control.awbLock"] = True
         req["android.control.aeLock"] = True
 
diff --git a/apps/CameraITS/tests/sensor_fusion/SensorFusion.pdf b/apps/CameraITS/tests/sensor_fusion/SensorFusion.pdf
new file mode 100644
index 0000000..2e390c7
--- /dev/null
+++ b/apps/CameraITS/tests/sensor_fusion/SensorFusion.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py b/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
new file mode 100644
index 0000000..49f47a9
--- /dev/null
+++ b/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
@@ -0,0 +1,377 @@
+# Copyright 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.
+
+import its.image
+import its.device
+import its.objects
+import time
+import math
+import pylab
+import os.path
+import matplotlib
+import matplotlib.pyplot
+import json
+import Image
+import numpy
+import cv2
+import bisect
+import scipy.spatial
+import sys
+
+NAME = os.path.basename(__file__).split(".")[0]
+
+# Capture 210 QVGA frames (which is 7s at 30fps)
+N = 210
+W,H = 320,240
+
+FEATURE_PARAMS = dict( maxCorners = 50,
+                       qualityLevel = 0.3,
+                       minDistance = 7,
+                       blockSize = 7 )
+
+LK_PARAMS = dict( winSize  = (15, 15),
+                  maxLevel = 2,
+                  criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT,
+                        10, 0.03))
+
+# Constants to convert between different time units (for clarity).
+SEC_TO_NSEC = 1000*1000*1000.0
+SEC_TO_MSEC = 1000.0
+MSEC_TO_NSEC = 1000*1000.0
+MSEC_TO_SEC = 1/1000.0
+NSEC_TO_SEC = 1/(1000*1000*1000.0)
+NSEC_TO_MSEC = 1/(1000*1000.0)
+
+# Pass/fail thresholds.
+THRESH_MAX_CORR_DIST = 0.005
+THRESH_MAX_SHIFT_MS = 2
+THRESH_MIN_ROT = 0.001
+
+def main():
+    """Test if image and motion sensor events are well synchronized.
+
+    The instructions for running this test are in the SensorFusion.pdf file in
+    the same directory as this test.
+
+    The command-line argument "replay" may be optionally provided. Without this
+    argument, the test will collect a new set of camera+gyro data from the
+    device and then analyze it (and it will also dump this data to files in the
+    current directory). If the "replay" argument is provided, then the script
+    will instead load the dumped data from a previous run and analyze that
+    instead. This can be helpful for developers who are digging for additional
+    information on their measurements.
+    """
+
+    # Collect or load the camera+gyro data. All gyro events as well as camera
+    # timestamps are in the "events" dictionary, and "frames" is a list of
+    # RGB images as numpy arrays.
+    if "replay" not in sys.argv:
+        events, frames = collect_data()
+    else:
+        events, frames = load_data()
+
+    # Compute the camera rotation displacements (rad) between each pair of
+    # adjacent frames.
+    cam_times = get_cam_times(events["cam"])
+    cam_rots = get_cam_rotations(frames)
+    if max(abs(cam_rots)) < THRESH_MIN_ROT:
+        print "Device wasn't moved enough"
+        assert(0)
+
+    # Find the best offset (time-shift) to align the gyro and camera motion
+    # traces; this function integrates the shifted gyro data between camera
+    # samples for a range of candidate shift values, and returns the shift that
+    # result in the best correlation.
+    offset = get_best_alignment_offset(cam_times, cam_rots, events["gyro"])
+
+    # Plot the camera and gyro traces after applying the best shift.
+    cam_times = cam_times + offset*SEC_TO_NSEC
+    gyro_rots = get_gyro_rotations(events["gyro"], cam_times)
+    plot_rotations(cam_rots, gyro_rots)
+
+    # Pass/fail based on the offset and also the correlation distance.
+    dist = scipy.spatial.distance.correlation(cam_rots,gyro_rots)
+    print "Best correlation of %f at shift of %.2fms"%(dist, offset*SEC_TO_MSEC)
+    assert(dist < THRESH_MAX_CORR_DIST)
+    assert(abs(offset) < THRESH_MAX_SHIFT_MS*MSEC_TO_SEC)
+
+def get_best_alignment_offset(cam_times, cam_rots, gyro_events):
+    """Find the best offset to align the camera and gyro traces.
+
+    Uses a correlation distance metric between the curves, where a smaller
+    value means that the curves are better-correlated.
+
+    Args:
+        cam_times: Array of N camera times, one for each frame.
+        cam_rots: Array of N-1 camera rotation displacements (rad).
+        gyro_events: List of gyro event objects.
+
+    Returns:
+        Offset (seconds) of the best alignment.
+    """
+    # Measure the corr. dist. over a shift of up to +/- 100ms (1ms step size).
+    # Get the shift corresponding to the best (lowest) score.
+    candidates = range(-100,101)
+    dists = []
+    for shift in candidates:
+        times = cam_times + shift*MSEC_TO_NSEC
+        gyro_rots = get_gyro_rotations(gyro_events, times)
+        dists.append(scipy.spatial.distance.correlation(cam_rots,gyro_rots))
+    best_corr_dist = min(dists)
+    best_shift = candidates[dists.index(best_corr_dist)]
+
+    # Fit a curve to the corr. dist. data to measure the minima more
+    # accurately, by looking at the correlation distances within a range of
+    # +/- 20ms from the measured best score; note that this will use fewer
+    # than the full +/- 20 range for the curve fit if the measured score
+    # (which is used as the center of the fit) is within 20ms of the edge of
+    # the +/- 100ms candidate range.
+    i = len(dists)/2 + best_shift
+    candidates = candidates[i-20:i+21]
+    dists = dists[i-20:i+21]
+    a,b,c = numpy.polyfit(candidates, dists, 2)
+    exact_best_shift = -b/(2*a)
+    if abs(best_shift - exact_best_shift) > 2.0 or a <= 0 or c <= 0:
+        print "Test failed; bad fit to time-shift curve"
+        assert(0)
+
+    xfit = [x/10.0 for x in xrange(candidates[0]*10,candidates[-1]*10)]
+    yfit = [a*x*x+b*x+c for x in xfit]
+    fig = matplotlib.pyplot.figure()
+    pylab.plot(candidates, dists, 'r', label="data")
+    pylab.plot(xfit, yfit, 'b', label="fit")
+    pylab.plot([exact_best_shift+x for x in [-0.1,0,0.1]], [0,0.01,0], 'b')
+    pylab.xlabel("Relative horizontal shift between curves (ms)")
+    pylab.ylabel("Correlation distance")
+    pylab.legend()
+    matplotlib.pyplot.savefig("%s_plot_shifts.png" % (NAME))
+
+    return exact_best_shift * MSEC_TO_SEC
+
+def plot_rotations(cam_rots, gyro_rots):
+    """Save a plot of the camera vs. gyro rotational measurements.
+
+    Args:
+        cam_rots: Array of N-1 camera rotation measurements (rad).
+        gyro_rots: Array of N-1 gyro rotation measurements (rad).
+    """
+    # For the plot, scale the rotations to be in degrees.
+    fig = matplotlib.pyplot.figure()
+    cam_rots = cam_rots * (360/(2*math.pi))
+    gyro_rots = gyro_rots * (360/(2*math.pi))
+    pylab.plot(range(len(cam_rots)), cam_rots, 'r', label="camera")
+    pylab.plot(range(len(gyro_rots)), gyro_rots, 'b', label="gyro")
+    pylab.legend()
+    pylab.xlabel("Camera frame number")
+    pylab.ylabel("Angular displacement between adjacent camera frames (deg)")
+    pylab.xlim([0, len(cam_rots)])
+    matplotlib.pyplot.savefig("%s_plot.png" % (NAME))
+
+def get_gyro_rotations(gyro_events, cam_times):
+    """Get the rotation values of the gyro.
+
+    Integrates the gyro data between each camera frame to compute an angular
+    displacement. Uses simple Euler approximation to implement the
+    integration.
+
+    Args:
+        gyro_events: List of gyro event objects.
+        cam_times: Array of N camera times, one for each frame.
+
+    Returns:
+        Array of N-1 gyro rotation measurements (rad).
+    """
+    all_times = numpy.array([e["time"] for e in gyro_events])
+    all_rots = numpy.array([e["z"] for e in gyro_events])
+    gyro_rots = []
+    # Integrate the gyro data between each pair of camera frame times.
+    for icam in range(len(cam_times)-1):
+        # Get the window of gyro samples within the current pair of frames.
+        tcam0 = cam_times[icam]
+        tcam1 = cam_times[icam+1]
+        igyrowindow0 = bisect.bisect(all_times, tcam0)
+        igyrowindow1 = bisect.bisect(all_times, tcam1)
+        sgyro = 0
+        # Integrate samples within the window.
+        for igyro in range(igyrowindow0, igyrowindow1):
+            vgyro0 = all_rots[igyro]
+            vgyro1 = all_rots[igyro+1]
+            tgyro0 = all_times[igyro]
+            tgyro1 = all_times[igyro+1]
+            vgyro = 0.5 * (vgyro0 + vgyro1)
+            deltatgyro = (tgyro1 - tgyro0) * NSEC_TO_SEC
+            sgyro += vgyro * deltatgyro
+        # Handle the fractional intervals at the sides of the window.
+        for side,igyro in enumerate([igyrowindow0-1, igyrowindow1]):
+            vgyro0 = all_rots[igyro]
+            vgyro1 = all_rots[igyro+1]
+            tgyro0 = all_times[igyro]
+            tgyro1 = all_times[igyro+1]
+            vgyro = 0.5 * (vgyro0 + vgyro1)
+            deltatgyro = (tgyro1 - tgyro0) * NSEC_TO_SEC
+            if side == 0:
+                f = (tcam0 - tgyro0) / (tgyro1 - tgyro0)
+                sgyro += vgyro * deltatgyro * (1.0 - f)
+            else:
+                f = (tcam1 - tgyro0) / (tgyro1 - tgyro0)
+                sgyro += vgyro * deltatgyro * f
+        gyro_rots.append(sgyro)
+    gyro_rots = numpy.array(gyro_rots)
+    return gyro_rots
+
+def get_cam_rotations(frames):
+    """Get the rotations of the camera between each pair of frames.
+
+    Takes N frames and returns N-1 angular displacements corresponding to the
+    rotations between adjacent pairs of frames, in radians.
+
+    Args:
+        frames: List of N images (as RGB numpy arrays).
+
+    Returns:
+        Array of N-1 camera rotation measurements (rad).
+    """
+    gframes = []
+    for frame in frames:
+        frame = (frame * 255.0).astype(numpy.uint8)
+        gframes.append(cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY))
+    rots = []
+    for i in range(1,len(gframes)):
+        gframe0 = gframes[i-1]
+        gframe1 = gframes[i]
+        p0 = cv2.goodFeaturesToTrack(gframe0, mask=None, **FEATURE_PARAMS)
+        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])
+        rots.append(rot)
+        if i == 1:
+            # Save a debug visualization of the features that are being
+            # tracked in the first frame.
+            frame = frames[i]
+            for x,y in p0[st==1]:
+                cv2.circle(frame, (x,y), 3, (100,100,255), -1)
+            its.image.write_image(frame, "%s_features.jpg"%(NAME))
+    return numpy.array(rots)
+
+def get_cam_times(cam_events):
+    """Get the camera frame times.
+
+    Args:
+        cam_events: List of (start_exposure, exposure_time, readout_duration)
+            tuples, one per captured frame, with times in nanoseconds.
+
+    Returns:
+        frame_times: Array of N times, one corresponding to the "middle" of
+            the exposure of each frame.
+    """
+    # Assign a time to each frame that assumes that the image is instantly
+    # captured in the middle of its exposure.
+    starts = numpy.array([start for start,exptime,readout in cam_events])
+    exptimes = numpy.array([exptime for start,exptime,readout in cam_events])
+    readouts = numpy.array([readout for start,exptime,readout in cam_events])
+    frame_times = starts + (exptimes + readouts) / 2.0
+    return frame_times
+
+def load_data():
+    """Load a set of previously captured data.
+
+    Returns:
+        events: Dictionary containing all gyro events and cam timestamps.
+        frames: List of RGB images as numpy arrays.
+    """
+    with open("%s_events.txt"%(NAME), "r") as f:
+        events = json.loads(f.read())
+    n = len(events["cam"])
+    frames = []
+    for i in range(n):
+        img = Image.open("%s_frame%03d.jpg"%(NAME,i))
+        w,h = img.size[0:2]
+        frames.append(numpy.array(img).reshape(h,w,3) / 255.0)
+    return events, frames
+
+def collect_data():
+    """Capture a new set of data from the device.
+
+    Captures both motion data and camera frames, while the user is moving
+    the device in a proscribed manner.
+
+    Returns:
+        events: Dictionary containing all gyro events and cam timestamps.
+        frames: List of RGB images as numpy arrays.
+    """
+    with its.device.ItsSession() as cam:
+        print "Starting sensor event collection"
+        cam.start_sensor_events()
+
+        # Sleep a few seconds for gyro events to stabilize.
+        time.sleep(5)
+
+        # TODO: Ensure that OIS is disabled; set to DISABLE and wait some time.
+
+        # Capture the frames.
+        props = cam.get_camera_properties()
+        fmt = {"format":"yuv", "width":W, "height":H}
+        s,e,_,_,_ = cam.do_3a(get_results=True)
+        req = its.objects.manual_capture_request(s, e)
+        print "Capturing %dx%d with sens. %d, exp. time %.1fms" % (
+                W, H, s, e*NSEC_TO_MSEC)
+        caps = cam.do_capture([req]*N, fmt)
+
+        # Get the gyro events.
+        print "Reading out sensor events"
+        gyro = cam.get_sensor_events()["gyro"]
+
+        # Combine the events into a single structure.
+        print "Dumping event data"
+        starts = [c["metadata"]["android.sensor.timestamp"] for c in caps]
+        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)}
+        with open("%s_events.txt"%(NAME), "w") as f:
+            f.write(json.dumps(events))
+
+        # Convert the frames to RGB.
+        print "Dumping frames"
+        frames = []
+        for i,c in enumerate(caps):
+            img = its.image.convert_capture_to_rgb_image(c)
+            frames.append(img)
+            its.image.write_image(img, "%s_frame%03d.jpg"%(NAME,i))
+
+        return events, frames
+
+def procrustes_rotation(X, Y):
+    """
+    Procrustes analysis determines a linear transformation (translation,
+    reflection, orthogonal rotation and scaling) of the points in Y to best
+    conform them to the points in matrix X, using the sum of squared errors
+    as the goodness of fit criterion.
+
+    Args:
+        X, Y: Matrices of target and input coordinates.
+
+    Returns:
+        The rotation component of the transformation that maps X to Y.
+    """
+    X0 = (X-X.mean(0)) / numpy.sqrt(((X-X.mean(0))**2.0).sum())
+    Y0 = (Y-Y.mean(0)) / numpy.sqrt(((Y-Y.mean(0))**2.0).sum())
+    U,s,Vt = numpy.linalg.svd(numpy.dot(X0.T, Y0),full_matrices=False)
+    return numpy.dot(Vt.T, U.T)
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tools/get_camera_ids.py b/apps/CameraITS/tools/get_camera_ids.py
new file mode 100644
index 0000000..010b046
--- /dev/null
+++ b/apps/CameraITS/tools/get_camera_ids.py
@@ -0,0 +1,37 @@
+# Copyright 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.
+
+import sys
+import its.device
+import its.objects
+import its.image
+
+def main():
+    """get camera ids and save it to disk.
+    """
+    out_path = ""
+    for s in sys.argv[1:]:
+        if s[:4] == "out=" and len(s) > 4:
+            out_path = s[4:]
+    # kind of weird we need to open a camera to get camera ids, but
+    # this is how ITS is working now.
+    with its.device.ItsSession() as cam:
+        camera_ids = cam.get_camera_ids()
+        if out_path != "":
+            with open(out_path, "w") as f:
+                for camera_id in camera_ids:
+                    f.write(camera_id + "\n")
+
+if __name__ == '__main__':
+    main()
diff --git a/apps/CameraITS/tools/run_all_tests.py b/apps/CameraITS/tools/run_all_tests.py
index 2202d5b..b56281d 100644
--- a/apps/CameraITS/tools/run_all_tests.py
+++ b/apps/CameraITS/tools/run_all_tests.py
@@ -18,6 +18,7 @@
 import subprocess
 import time
 import sys
+import textwrap
 import its.device
 
 def main():
@@ -38,6 +39,8 @@
             "test_ae_precapture_trigger",
             "test_black_white",
             "test_crop_region_raw",
+            "test_ev_compensation_advanced",
+            "test_ev_compensation_basic",
             "test_locked_burst",
             "test_yuv_plus_jpeg"
         ]
@@ -55,62 +58,111 @@
 
     # Make output directories to hold the generated files.
     topdir = tempfile.mkdtemp()
-    for d in scenes:
-        os.mkdir(os.path.join(topdir, d))
     print "Saving output files to:", topdir, "\n"
 
-    # determine camera id
-    camera_id = 0
+    camera_ids = []
     for s in sys.argv[1:]:
         if s[:7] == "camera=" and len(s) > 7:
-            camera_id = s[7:]
+            camera_ids.append(s[7:])
 
-    # Run each test, capturing stdout and stderr.
-    numpass = 0
-    numskip = 0
-    numnotmandatedfail = 0
-    numfail = 0
-    for (scene,testname,testpath) in tests:
-        cmd = ['python', os.path.join(os.getcwd(),testpath)] + sys.argv[1:]
-        outdir = os.path.join(topdir,scene)
-        outpath = os.path.join(outdir,testname+"_stdout.txt")
-        errpath = os.path.join(outdir,testname+"_stderr.txt")
-        t0 = time.time()
-        with open(outpath,"w") as fout, open(errpath,"w") as ferr:
-            retcode = subprocess.call(cmd,stderr=ferr,stdout=fout,cwd=outdir)
-        t1 = time.time()
+    # user doesn't specify camera id, run through all cameras
+    if not camera_ids:
+        camera_ids_path = os.path.join(topdir, "camera_ids.txt")
+        out_arg = "out=" + camera_ids_path
+        cmd = ['python',
+               os.path.join(os.getcwd(),"tools/get_camera_ids.py"), out_arg]
+        retcode = subprocess.call(cmd,cwd=topdir)
+        assert(retcode == 0)
+        with open(camera_ids_path, "r") as f:
+            for line in f:
+                camera_ids.append(line.replace('\n', ''))
 
-        if retcode == 0:
-            retstr = "PASS "
-            numpass += 1
-        elif retcode == SKIP_RET_CODE:
-            retstr = "SKIP "
-            numskip += 1
-        elif retcode != 0 and testname in NOT_YET_MANDATED[scene]:
-            retstr = "FAIL*"
-            numnotmandatedfail += 1
+    print "Running ITS on the following cameras:", camera_ids
+
+    for camera_id in camera_ids:
+        # Loop capturing images until user confirm test scene is correct
+        camera_id_arg = "camera=" + camera_id
+        print "Preparing to run ITS on camera", camera_id
+
+        os.mkdir(os.path.join(topdir, camera_id))
+        for d in scenes:
+            os.mkdir(os.path.join(topdir, camera_id, d))
+
+        out_path = os.path.join(topdir, camera_id, "scene.jpg")
+        out_arg = "out=" + out_path
+        cmd = ['python',
+               os.path.join(os.getcwd(),"tools/validate_scene.py"),
+               camera_id_arg, out_arg]
+        retcode = subprocess.call(cmd,cwd=topdir)
+        assert(retcode == 0)
+
+        print "Start running ITS on camera: ", camera_id
+        # Run each test, capturing stdout and stderr.
+        summary = "ITS test result summary for camera " + camera_id + "\n"
+        numpass = 0
+        numskip = 0
+        numnotmandatedfail = 0
+        numfail = 0
+
+        for (scene,testname,testpath) in tests:
+            cmd = ['python', os.path.join(os.getcwd(),testpath)] + \
+                  sys.argv[1:] + [camera_id_arg]
+            outdir = os.path.join(topdir,camera_id,scene)
+            outpath = os.path.join(outdir,testname+"_stdout.txt")
+            errpath = os.path.join(outdir,testname+"_stderr.txt")
+            t0 = time.time()
+            with open(outpath,"w") as fout, open(errpath,"w") as ferr:
+                retcode = subprocess.call(cmd,stderr=ferr,stdout=fout,cwd=outdir)
+            t1 = time.time()
+
+            if retcode == 0:
+                retstr = "PASS "
+                numpass += 1
+            elif retcode == SKIP_RET_CODE:
+                retstr = "SKIP "
+                numskip += 1
+            elif retcode != 0 and testname in NOT_YET_MANDATED[scene]:
+                retstr = "FAIL*"
+                numnotmandatedfail += 1
+            else:
+                retstr = "FAIL "
+                numfail += 1
+
+            msg = "%s %s/%s [%.1fs]" % (retstr, scene, testname, t1-t0)
+            print msg
+            summary += msg + "\n"
+            if retcode != 0 and retcode != SKIP_RET_CODE:
+                # Dump the stderr if the test fails
+                with open (errpath, "r") as error_file:
+                    errors = error_file.read()
+                    summary += errors + "\n"
+
+        if numskip > 0:
+            skipstr = ", %d test%s skipped" % (numskip, "s" if numskip > 1 else "")
         else:
-            retstr = "FAIL "
-            numfail += 1
+            skipstr = ""
 
-        print "%s %s/%s [%.1fs]" % (retstr, scene, testname, t1-t0)
+        test_result = "\n%d / %d tests passed (%.1f%%)%s" % (
+                numpass + numnotmandatedfail, len(tests) - numskip,
+                100.0 * float(numpass + numnotmandatedfail) / (len(tests) - numskip)
+                    if len(tests) != numskip else 100.0,
+                skipstr)
+        print test_result
+        summary += test_result + "\n"
 
-    if numskip > 0:
-        skipstr = ", %d test%s skipped" % (numskip, "s" if numskip > 1 else "")
-    else:
-        skipstr = ""
+        if numnotmandatedfail > 0:
+            msg = "(*) tests are not yet mandated"
+            print msg
+            summary += msg + "\n"
 
-    print "\n%d / %d tests passed (%.1f%%)%s" % (
-            numpass + numnotmandatedfail, len(tests) - numskip,
-            100.0 * float(numpass + numnotmandatedfail) / (len(tests) - numskip)
-                if len(tests) != numskip else 100.0,
-            skipstr)
+        result = numfail == 0
+        print "Reporting ITS result to CtsVerifier"
+        summary_path = os.path.join(topdir, camera_id, "summary.txt")
+        with open(summary_path, "w") as f:
+            f.write(summary)
+        its.device.report_result(camera_id, result, summary_path)
 
-    if numnotmandatedfail > 0:
-        print "(*) tests are not yet mandated"
-
-    its.device.report_result(camera_id, numfail == 0)
+    print "ITS tests finished. Please go back to CtsVerifier and proceed"
 
 if __name__ == '__main__':
     main()
-
diff --git a/apps/CameraITS/tools/validate_scene.py b/apps/CameraITS/tools/validate_scene.py
new file mode 100644
index 0000000..e1e89f2
--- /dev/null
+++ b/apps/CameraITS/tools/validate_scene.py
@@ -0,0 +1,60 @@
+# Copyright 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.
+
+import sys
+import its.device
+import its.objects
+import its.image
+
+def main():
+    """capture a yuv image and save it to argv[1]
+    """
+    camera_id = -1
+    out_path = ""
+    for s in sys.argv[1:]:
+        if s[:7] == "camera=" and len(s) > 7:
+            camera_id = s[7:]
+        elif s[:4] == "out=" and len(s) > 4:
+            out_path = s[4:]
+
+    if camera_id == -1:
+        print "Error: need to specify which camera to use"
+        assert(False)
+
+    with its.device.ItsSession() as cam:
+        raw_input("Press Enter after placing camera " + camera_id +
+                " to frame the test scene")
+        # Converge 3A prior to capture.
+        cam.do_3a(do_af=True, lock_ae=True, lock_awb=True)
+        props = cam.get_camera_properties()
+        req = its.objects.fastest_auto_capture_request(props)
+        req["android.control.awbLock"] = True
+        req["android.control.aeLock"] = True
+        while True:
+            print "Capture an image to check the test scene"
+            cap = cam.do_capture(req)
+            img = its.image.convert_capture_to_rgb_image(cap)
+            if out_path != "":
+                its.image.write_image(img, out_path)
+            print "Please check scene setup in", out_path
+            choice = raw_input(
+                "Is the image okay for ITS scene1? (Y/N)").lower()
+            if choice == "y":
+                break
+            else:
+                raw_input("Press Enter after placing camera " + camera_id +
+                          " to frame the test scene")
+
+if __name__ == '__main__':
+    main()
diff --git a/apps/CtsVerifier/Android.mk b/apps/CtsVerifier/Android.mk
index e370c81..87f962f 100644
--- a/apps/CtsVerifier/Android.mk
+++ b/apps/CtsVerifier/Android.mk
@@ -25,7 +25,10 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-Iaidl-files-under, src)
 
-LOCAL_STATIC_JAVA_LIBRARIES := cts-sensors-tests ctstestrunner android-ex-camera2
+LOCAL_STATIC_JAVA_LIBRARIES := android-ex-camera2 \
+                               compatibility-common-util-devicesidelib_v2 \
+                               cts-sensors-tests \
+                               ctstestrunner \
 
 LOCAL_PACKAGE_NAME := CtsVerifier
 
@@ -40,10 +43,13 @@
 
 include $(BUILD_PACKAGE)
 
+notification-bot := $(call intermediates-dir-for,APPS,NotificationBot)/package.apk
+
 # Builds and launches CTS Verifier on a device.
 .PHONY: cts-verifier
-cts-verifier: CtsVerifier adb
+cts-verifier: CtsVerifier adb NotificationBot
 	adb install -r $(PRODUCT_OUT)/data/app/CtsVerifier/CtsVerifier.apk \
+		&& adb install -r $(notification-bot) \
 		&& adb shell "am start -n com.android.cts.verifier/.CtsVerifierActivity"
 
 #
@@ -79,10 +85,11 @@
 $(verifier-zip) : $(HOST_OUT)/bin/cts-usb-accessory
 endif
 $(verifier-zip) : $(HOST_OUT)/CameraITS
-
+$(verifier-zip) : $(notification-bot)
 $(verifier-zip) : $(call intermediates-dir-for,APPS,CtsVerifier)/package.apk | $(ACP)
 		$(hide) mkdir -p $(verifier-dir)
 		$(hide) $(ACP) -fp $< $(verifier-dir)/CtsVerifier.apk
+		$(ACP) -fp $(notification-bot) $(verifier-dir)/NotificationBot.apk
 ifeq ($(HOST_OS),linux)
 		$(hide) $(ACP) -fp $(HOST_OUT)/bin/cts-usb-accessory $(verifier-dir)/cts-usb-accessory
 endif
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 33244cf..ed0da02 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -17,8 +17,8 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
       package="com.android.cts.verifier"
-      android:versionCode="4"
-      android:versionName="5.0_r2">
+      android:versionCode="5"
+      android:versionName="5.0_r2.5">
 
     <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="21"/>
 
@@ -55,6 +55,8 @@
     <uses-permission android:name="android.permission.READ_CONTACTS"/>
     <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+    <uses-permission android:name="com.android.providers.tv.permission.READ_EPG_DATA" />
+    <uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
 
     <!-- Needed by the Audio Quality Verifier to store the sound samples that will be mailed. -->
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
@@ -240,8 +242,8 @@
         <service android:name=".bluetooth.BleScannerService"
                 android:label="@string/ble_scanner_service_name" />
 
-        <!-- TODO: Enable when test quality issues listed in b/18283088 is resolved -->
-        <!-- activity android:name=".bluetooth.BleClientTestActivity"
+        <!-- Uncomment until b/15657182, b/18283088 fixed
+        <activity android:name=".bluetooth.BleClientStartActivity"
                 android:label="@string/ble_client_test_name"
                 android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
@@ -252,98 +254,9 @@
             <meta-data android:name="test_parent" android:value="com.android.cts.verifier.bluetooth.BluetoothTestActivity" />
             <meta-data android:name="test_required_features"
                        android:value="android.hardware.bluetooth_le"/>
-        </activity -->
-
-        <activity android:name=".bluetooth.BleClientConnectActivity"
-                android:label="@string/ble_client_connect_name"
-                android:configChanges="keyboardHidden|orientation|screenSize">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.cts.intent.category.MANUAL_TEST" />
-            </intent-filter>
-            <meta-data android:name="test_category" android:value="@string/bt_le" />
-            <meta-data android:name="test_parent" android:value="com.android.cts.verifier.bluetooth.BleClientTestActivity" />
         </activity>
 
-        <activity android:name=".bluetooth.BleDiscoverServiceActivity"
-                android:label="@string/ble_discover_service_name"
-                android:configChanges="keyboardHidden|orientation|screenSize">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.cts.intent.category.MANUAL_TEST" />
-            </intent-filter>
-            <meta-data android:name="test_category" android:value="@string/bt_le" />
-            <meta-data android:name="test_parent" android:value="com.android.cts.verifier.bluetooth.BleClientTestActivity" />
-        </activity>
-
-        <activity android:name=".bluetooth.BleClientCharacteristicActivity"
-                android:label="@string/ble_client_characteristic_name"
-                android:configChanges="keyboardHidden|orientation|screenSize">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.cts.intent.category.MANUAL_TEST" />
-            </intent-filter>
-            <meta-data android:name="test_category" android:value="@string/bt_le" />
-            <meta-data android:name="test_parent" android:value="com.android.cts.verifier.bluetooth.BleClientTestActivity" />
-        </activity>
-
-        <activity android:name=".bluetooth.BleNotifyCharacteristicActivity"
-                android:label="@string/ble_notify_characteristic_name"
-                android:configChanges="keyboardHidden|orientation|screenSize">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.cts.intent.category.MANUAL_TEST" />
-            </intent-filter>
-            <meta-data android:name="test_category" android:value="@string/bt_le" />
-            <meta-data android:name="test_parent" android:value="com.android.cts.verifier.bluetooth.BleClientTestActivity" />
-        </activity>
-
-        <activity android:name=".bluetooth.BleClientDescriptorActivity"
-                android:label="@string/ble_client_descriptor_name"
-                android:configChanges="keyboardHidden|orientation|screenSize">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.cts.intent.category.MANUAL_TEST" />
-            </intent-filter>
-            <meta-data android:name="test_category" android:value="@string/bt_le" />
-            <meta-data android:name="test_parent" android:value="com.android.cts.verifier.bluetooth.BleClientTestActivity" />
-        </activity>
-
-        <activity android:name=".bluetooth.BleReliableWriteActivity"
-                android:label="@string/ble_reliable_write_name"
-                android:configChanges="keyboardHidden|orientation|screenSize">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.cts.intent.category.MANUAL_TEST" />
-            </intent-filter>
-            <meta-data android:name="test_category" android:value="@string/bt_le" />
-            <meta-data android:name="test_parent" android:value="com.android.cts.verifier.bluetooth.BleClientTestActivity" />
-        </activity>
-
-        <activity android:name=".bluetooth.BleReadRssiActivity"
-                android:label="@string/ble_read_rssi_name"
-                android:configChanges="keyboardHidden|orientation|screenSize">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.cts.intent.category.MANUAL_TEST" />
-            </intent-filter>
-            <meta-data android:name="test_category" android:value="@string/bt_le" />
-            <meta-data android:name="test_parent" android:value="com.android.cts.verifier.bluetooth.BleClientTestActivity" />
-        </activity>
-
-        <activity android:name=".bluetooth.BleClientDisconnectActivity"
-                android:label="@string/ble_client_disconnect_name"
-                android:configChanges="keyboardHidden|orientation|screenSize">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.cts.intent.category.MANUAL_TEST" />
-            </intent-filter>
-            <meta-data android:name="test_category" android:value="@string/bt_le" />
-            <meta-data android:name="test_parent" android:value="com.android.cts.verifier.bluetooth.BleClientTestActivity" />
-        </activity>
-
-        <!-- TODO: Enable when test quality issues listed in b/18283088 is resolved -->
-        <!-- activity android:name=".bluetooth.BleServerStartActivity"
+        <activity android:name=".bluetooth.BleServerStartActivity"
                 android:label="@string/ble_server_start_name"
                 android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
@@ -354,10 +267,9 @@
             <meta-data android:name="test_parent" android:value="com.android.cts.verifier.bluetooth.BluetoothTestActivity" />
             <meta-data android:name="test_required_features"
                        android:value="android.hardware.bluetooth_le"/>
-        </activity -->
+        </activity> -->
 
-        <!-- TODO: Enable when test quality issues listed in b/18282549 is resolved -->
-        <!-- activity android:name=".bluetooth.BleScannerTestActivity"
+        <activity android:name=".bluetooth.BleScannerTestActivity"
                 android:label="@string/ble_scanner_test_name"
                 android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
@@ -368,7 +280,7 @@
             <meta-data android:name="test_parent" android:value="com.android.cts.verifier.bluetooth.BluetoothTestActivity" />
             <meta-data android:name="test_required_features"
                        android:value="android.hardware.bluetooth_le"/>
-        </activity -->
+        </activity>
 
         <activity android:name=".bluetooth.BleScannerPowerLevelActivity"
                 android:label="@string/ble_power_level_name"
@@ -381,6 +293,7 @@
             <meta-data android:name="test_parent" android:value="com.android.cts.verifier.bluetooth.BleScannerTestActivity" />
         </activity>
 
+        <!-- Comment out until we have a better way to validate the hardware scan filter
         <activity android:name=".bluetooth.BleScannerHardwareScanFilterActivity"
                 android:label="@string/ble_scanner_scan_filter_name"
                 android:configChanges="keyboardHidden|orientation|screenSize">
@@ -391,9 +304,9 @@
             <meta-data android:name="test_category" android:value="@string/bt_le" />
             <meta-data android:name="test_parent" android:value="com.android.cts.verifier.bluetooth.BleScannerTestActivity" />
         </activity>
+        -->
 
-        <!-- TODO: Enable when test quality issues listed in b/18282549 is resolved -->
-        <!-- activity android:name=".bluetooth.BleAdvertiserTestActivity"
+        <activity android:name=".bluetooth.BleAdvertiserTestActivity"
                 android:label="@string/ble_advertiser_test_name"
                 android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
@@ -404,7 +317,7 @@
             <meta-data android:name="test_parent" android:value="com.android.cts.verifier.bluetooth.BluetoothTestActivity" />
             <meta-data android:name="test_required_features"
                        android:value="android.hardware.bluetooth_le"/>
-         </activity -->
+         </activity>
 
         <activity android:name=".bluetooth.BleAdvertiserPowerLevelActivity"
                 android:label="@string/ble_power_level_name"
@@ -417,6 +330,7 @@
             <meta-data android:name="test_parent" android:value="com.android.cts.verifier.bluetooth.BleAdvertiserTestActivity" />
         </activity>
 
+        <!-- Comment out until we have a better way to validate the hardware scan filter
         <activity android:name=".bluetooth.BleAdvertiserHardwareScanFilterActivity"
                 android:label="@string/ble_advertiser_scan_filter_name"
                 android:configChanges="keyboardHidden|orientation|screenSize">
@@ -427,6 +341,7 @@
             <meta-data android:name="test_category" android:value="@string/bt_le" />
             <meta-data android:name="test_parent" android:value="com.android.cts.verifier.bluetooth.BleAdvertiserTestActivity" />
         </activity>
+        -->
 
         <activity android:name=".suid.SuidFilesActivity"
                 android:label="@string/suid_files"
@@ -587,6 +502,10 @@
                 android:label="@string/nfc_hce_payment_dynamic_aids_emulator"
                 android:configChanges="keyboardHidden|orientation|screenSize" />
 
+        <activity android:name=".nfc.hce.LargeNumAidsEmulatorActivity"
+                  android:label="@string/nfc_hce_large_num_aids_emulator"
+                  android:configChanges="keyboardHidden|orientation|screenSize" />
+
         <activity android:name=".nfc.hce.PrefixPaymentEmulatorActivity"
                 android:label="@string/nfc_hce_payment_prefix_aids_emulator"
                 android:configChanges="keyboardHidden|orientation|screenSize" />
@@ -722,6 +641,15 @@
             </intent-filter>
             <meta-data android:name="android.nfc.cardemulation.host_apdu_service" android:resource="@xml/access_prefix_aid_list"/>
         </service>
+        <service android:name=".nfc.hce.LargeNumAidsService" android:exported="true"
+                 android:permission="android.permission.BIND_NFC_SERVICE"
+                 android:enabled="false">
+            <intent-filter>
+                <action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+            <meta-data android:name="android.nfc.cardemulation.host_apdu_service" android:resource="@xml/payment_aid_list_1"/>
+        </service>
 
         <!-- Service used for Camera ITS tests -->
         <service android:name=".camera.its.ItsService" >
@@ -901,6 +829,10 @@
             <meta-data android:name="test_category" android:value="@string/test_category_location" />
             <meta-data android:name="test_required_features"
                     android:value="android.hardware.location.network:android.hardware.location.gps" />
+            <meta-data android:name="test_excluded_features"
+                    android:value="android.hardware.type.television" />
+            <meta-data android:name="test_excluded_features"
+                    android:value="android.software.leanback" />
         </activity>
         <activity android:name=".location.LocationModeBatterySavingTestActivity"
                 android:label="@string/location_mode_battery_saving_test">
@@ -910,6 +842,10 @@
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_location" />
             <meta-data android:name="test_required_features" android:value="android.hardware.location.network" />
+            <meta-data android:name="test_excluded_features"
+                    android:value="android.hardware.type.television" />
+            <meta-data android:name="test_excluded_features"
+                    android:value="android.software.leanback" />
         </activity>
         <activity android:name=".location.LocationModeDeviceOnlyTestActivity"
                 android:label="@string/location_mode_device_only_test">
@@ -1071,13 +1007,26 @@
             <meta-data android:name="test_category" android:value="@string/test_category_notifications" />
         </activity>
 
-        <activity android:name=".notifications.NotificationAttentionManagementVerifierActivity"
+        <activity android:name=".notifications.AttentionManagementVerifierActivity"
                 android:label="@string/attention_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.cts.intent.category.MANUAL_TEST" />
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_notifications" />
+            <meta-data android:name="test_excluded_features"
+                    android:value="android.hardware.type.watch" />
+        </activity>
+
+        <activity android:name=".notifications.PackagePriorityVerifierActivity"
+                android:label="@string/package_priority_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_notifications" />
+            <meta-data android:name="test_excluded_features"
+                       android:value="android.hardware.type.watch:android.software.leanback" />
         </activity>
 
         <service android:name=".notifications.MockListener"
@@ -1089,7 +1038,8 @@
             </intent-filter>
         </service>
 
-        <service  android:name=".notifications.NotificationListenerVerifierActivity$DismissService"/>
+        <service  android:name=".notifications.InteractiveVerifierActivity$DismissService"/>
+
         <activity android:name=".security.CAInstallNotificationVerifierActivity"
                 android:label="@string/cacert_test">
             <intent-filter>
@@ -1296,7 +1246,8 @@
                 <category android:name="android.cts.intent.category.MANUAL_TEST" />
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_projection" />
-            <meta-data android:name="test_required_features" android:value="android.hardware.faketouch" />
+            <meta-data android:name="test_required_features"
+                       android:value="android.hardware.faketouch:android.hardware.touchscreen.multitouch" />
         </activity>
 
 
@@ -1326,8 +1277,7 @@
         </activity>
 
 
-        <!-- TODO: enable when the test can be executed without leaving marks -->
-        <!-- activity android:name=".managedprovisioning.ByodFlowTestActivity"
+        <activity android:name=".managedprovisioning.ByodFlowTestActivity"
                 android:launchMode="singleTask"
                 android:label="@string/provisioning_byod">
             <intent-filter>
@@ -1340,12 +1290,20 @@
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_managed_provisioning" />
             <meta-data android:name="test_required_features" android:value="android.software.managed_users:android.software.device_admin" />
-        </activity-->
+        </activity>
 
         <activity android:name=".managedprovisioning.ByodHelperActivity">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.BYOD_QUERY" />
                 <action android:name="com.android.cts.verifier.managedprovisioning.BYOD_REMOVE" />
+                <action android:name="com.android.cts.verifier.managedprovisioning.BYOD_INSTALL_APK" />
+                <category android:name="android.intent.category.DEFAULT"></category>
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".managedprovisioning.ByodIconSamplerActivity">
+            <intent-filter>
+                <action android:name="com.android.cts.verifier.managedprovisioning.BYOD_SAMPLE_ICON" />
                 <category android:name="android.intent.category.DEFAULT"></category>
             </intent-filter>
         </activity>
@@ -1357,6 +1315,14 @@
             </intent-filter>
         </activity>
 
+        <activity android:name=".managedprovisioning.WorkNotificationTestActivity">
+            <intent-filter>
+                <action android:name="com.android.cts.verifier.managedprovisioning.WORK_NOTIFICATION" />
+                <action android:name="com.android.cts.verifier.managedprovisioning.CLEAR_WORK_NOTIFICATION" />
+                <category android:name="android.intent.category.DEFAULT"></category>
+            </intent-filter>
+        </activity>
+
         <receiver android:name=".managedprovisioning.DeviceAdminTestReceiver"
                 android:label="@string/provisioning_byod_device_admin"
                 android:permission="android.permission.BIND_DEVICE_ADMIN">
@@ -1407,6 +1373,82 @@
         <service android:name=".jobscheduler.MockJobService"
             android:permission="android.permission.BIND_JOB_SERVICE"/>
 
+        <!-- Used by the SensorTestScreenManipulator to reset the screen timeout after turn off. -->
+        <activity android:name=".os.TimeoutResetActivity"/>
+
+        <activity android:name=".tv.TvInputDiscoveryTestActivity"
+                android:label="@string/tv_input_discover_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_tv" />
+            <meta-data android:name="test_required_features"
+                    android:value="android.software.live_tv" />
+        </activity>
+
+        <activity android:name=".tv.ParentalControlTestActivity"
+                android:label="@string/tv_parental_control_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_tv" />
+            <meta-data android:name="test_required_features"
+                    android:value="android.software.live_tv" />
+        </activity>
+
+        <activity android:name=".tv.MultipleTracksTestActivity"
+                android:label="@string/tv_multiple_tracks_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_tv" />
+            <meta-data android:name="test_required_features"
+                    android:value="android.software.live_tv" />
+        </activity>
+
+        <activity android:name=".screenpinning.ScreenPinningTestActivity"
+            android:label="@string/screen_pinning_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_other" />
+            <meta-data android:name="test_excluded_features"
+                       android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.watch" />
+        </activity>
+
+        <activity android:name=".tv.MockTvInputSettingsActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".tv.MockTvInputSetupActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+            </intent-filter>
+        </activity>
+
+        <service android:name=".tv.MockTvInputService"
+            android:permission="android.permission.BIND_TV_INPUT">
+            <intent-filter>
+                <action android:name="android.media.tv.TvInputService" />
+            </intent-filter>
+            <meta-data android:name="android.media.tv.input"
+                android:resource="@xml/mock_tv_input_service" />
+        </service>
+
+        <receiver android:name=".tv.TvInputReceiver">
+            <intent-filter>
+                <action android:name="android.media.tv.action.QUERY_CONTENT_RATING_SYSTEMS" />
+            </intent-filter>
+            <meta-data android:name="android.media.tv.metadata.CONTENT_RATING_SYSTEMS"
+                android:resource="@xml/mock_content_rating_systems" />
+        </receiver>
+
     </application>
 
 </manifest>
diff --git a/apps/CtsVerifier/assets/scripts/execute_power_tests.py b/apps/CtsVerifier/assets/scripts/execute_power_tests.py
index d1c2dac..d07cb36 100755
--- a/apps/CtsVerifier/assets/scripts/execute_power_tests.py
+++ b/apps/CtsVerifier/assets/scripts/execute_power_tests.py
@@ -25,57 +25,108 @@
 import pkgutil
 import threading
 import Queue
+import traceback
+import math
+import bisect
+from bisect import bisect_left
 
-# queue to signal thread to exit
-signal_exit_q = Queue.Queue()
-signal_abort = Queue.Queue()
+"""
+scipy, numpy and matplotlib are python packages that can be installed
+from: http://www.scipy.org/
+
+"""
+import scipy
+import matplotlib.pyplot as plt
 
 # let this script know about the power monitor implementations
 sys.path = [os.path.basename(__file__)] + sys.path
-available_monitors = [name for _, name, _ in pkgutil.iter_modules(
-    [os.path.join(os.path.dirname(__file__),'power_monitors')]) if not name.startswith('_')]
+available_monitors = [
+    name
+    for _, name, _ in pkgutil.iter_modules(
+        [os.path.join(os.path.dirname(__file__), "power_monitors")])
+    if not name.startswith("_")]
 
-APK = os.path.join( os.path.dirname(__file__), '..', "CtsVerifier.apk")
+APK = os.path.join(os.path.dirname(__file__), "..", "CtsVerifier.apk")
 
 FLAGS = flags.FLAGS
 
-# whether to use a strict delay to ensure screen is off, or attempt to use power measurements
-USE_STRICT_DELAY = False
-if USE_STRICT_DELAY:
-    DELAY_SCREEN_OFF = 30.0
-else:
-    DELAY_SCREEN_OFF = 2.0
+# DELAY_SCREEN_OFF is the number of seconds to wait for baseline state
+DELAY_SCREEN_OFF = 20.0
 
 # whether to log data collected to a file for each sensor run:
 LOG_DATA_TO_FILE = True
 
 logging.getLogger().setLevel(logging.ERROR)
 
+
 def do_import(name):
     """import a module by name dynamically"""
     mod = __import__(name)
-    components = name.split('.')
+    components = name.split(".")
     for comp in components[1:]:
         mod = getattr(mod, comp)
     return mod
 
+class PowerTestException(Exception):
+    """
+    Definition of specialized Exception class for CTS power tests
+    """
+    def __init__(self, message):
+        self._error_message = message
+    def __str__(self):
+        return self._error_message
 
 class PowerTest:
-    """Class to run a suite of power tests"""
+    """Class to run a suite of power tests. This has methods for obtaining
+    measurements from the power monitor (through the driver) and then
+    processing it to determine baseline and AP suspend state and
+    measure ampere draw of various sensors.
+    Ctrl+C causes a keyboard interrupt exception which terminates the test."""
 
     # Thresholds for max allowed power usage per sensor tested
-    MAX_ACCEL_POWER = 0.08  # Amps
-    MAX_MAG_POWER = 0.08  # Amps
-    MAX_GYRO_POWER = 0.08  # Amps
-    MAX_SIGMO_POWER = 0.08 # Amps
-    MAX_STEP_COUNTER_POWER = 0.08 # Amps
-    MAX_STEP_DETECTOR_POWER = 0.08 # Amps
+    # TODO: Accel, Mag and Gyro have no maximum power specified in the CDD;
+    # the following numbers are bogus and will be replaced soon by what
+    # the device reports (from Sensor.getPower())
+    MAX_ACCEL_AMPS = 0.08  # Amps
+    MAX_MAG_AMPS = 0.08  # Amps
+    MAX_GYRO_AMPS = 0.08  # Amps
+    MAX_SIGMO_AMPS = 0.08  # Amps
 
-
-    PORT = 0  # any available port
+    # TODO: The following numbers for step counter, etc must be replaced by
+    # the numbers specified in CDD for low-power sensors. The expected current
+    # draw must be computed from the specified power and the voltage used to
+    # power the device (specified from a config file).
+    MAX_STEP_COUNTER_AMPS = 0.08  # Amps
+    MAX_STEP_DETECTOR_AMPS = 0.08  # Amps
+    # The variable EXPECTED_AMPS_VARIATION_HALF_RANGE denotes the expected
+    # variation of  the ampere measurements
+    # around the mean value at baseline state. i.e. we expect most of the
+    # ampere measurements at baseline state to vary around the mean by
+    # between +/- of the number below
+    EXPECTED_AMPS_VARIATION_HALF_RANGE = 0.0005
+    # The variable THRESHOLD_BASELINE_SAMPLES_FRACTION denotes the minimum fraction of samples that must
+    # be in the range of variation defined by EXPECTED_AMPS_VARIATION_HALF_RANGE
+    # around the mean baseline for us to decide that the phone has settled into
+    # its baseline state
+    THRESHOLD_BASELINE_SAMPLES_FRACTION = 0.86
+    # The variable MAX_PERCENTILE_AP_SCREEN_OFF_AMPS denotes the maximum ampere
+    # draw that the device can consume when it has gone to suspend state with
+    # one or more sensors registered and batching samples (screen and AP are
+    # off in this case)
+    MAX_PERCENTILE_AP_SCREEN_OFF_AMPS = 0.030  # Amps
+    # The variable PERCENTILE_MAX_AP_SCREEN_OFF denotes the fraction of ampere
+    # measurements that must be below the specified maximum amperes
+    # MAX_PERCENTILE_AP_SCREEN_OFF_AMPS for us to decide that the phone has
+    # reached suspend state.
+    PERCENTILE_MAX_AP_SCREEN_OFF = 0.95
     DOMAIN_NAME = "/android/cts/powertest"
+    # SAMPLE_COUNT_NOMINAL denotes the typical number of measurements of amperes
+    # to collect from the power monitor
     SAMPLE_COUNT_NOMINAL = 1000
+    # RATE_NOMINAL denotes the nominal frequency at which ampere measurements
+    # are taken from the monsoon power monitor
     RATE_NOMINAL = 100
+    ENABLE_PLOTTING = False
 
     REQUEST_EXTERNAL_STORAGE = "EXTERNAL STORAGE?"
     REQUEST_EXIT = "EXIT"
@@ -87,103 +138,157 @@
     REQUEST_SCREEN_OFF = "SCREEN OFF"
     REQUEST_SHOW_MESSAGE = "MESSAGE %s"
 
+    NEGATIVE_AMPERE_ERROR_MESSAGE = (
+        "Negative ampere draw measured, possibly due to power "
+        "supply from USB cable. Check the setup of device and power "
+        "monitor to make sure that the device is not connected "
+        "to machine via USB directly. The device should be "
+        "connected to the USB slot in the power monitor. It is okay "
+        "to change the wiring when the test is in progress.")
 
-    def __init__(self):
+
+    def __init__(self, max_baseline_amps):
+        """
+        Args:
+            max_baseline_amps: The maximum value of baseline amperes
+                    that we expect the device to consume at baseline state.
+                    This can be different between models of phones.
+        """
         power_monitors = do_import("power_monitors.%s" % FLAGS.power_monitor)
         testid = time.strftime("%d_%m_%Y__%H__%M_%S")
         self._power_monitor = power_monitors.Power_Monitor(log_file_id = testid)
+        self._tcp_connect_port = 0  # any available port
         print ("Establishing connection to device...")
         self.setUsbEnabled(True)
         status = self._power_monitor.GetStatus()
         self._native_hz = status["sampleRate"] * 1000
+        # the following describes power test being run (i.e on what sensor
+        # and what type of test. This is used for logging.
         self._current_test = "None"
-        self._external_storage = self.executeOnDevice(PowerTest.REQUEST_EXTERNAL_STORAGE,
-                                                      reportErrors=True)
+        self._external_storage = self.executeOnDevice(PowerTest.REQUEST_EXTERNAL_STORAGE)
+        self._max_baseline_amps = max_baseline_amps
 
     def __del__(self):
         self.finalize()
 
     def finalize(self):
         """To be called upon termination of host connection to device"""
-        if PowerTest.PORT > 0:
-            # tell device side to exit connection loop, and remove the forwarding connection
-            self.executeOnDevice(PowerTest.REQUEST_EXIT, reportErrors=False)
-            self.executeLocal("adb forward --remove tcp:%d" % PowerTest.PORT)
-        PowerTest.PORT = 0
+        if self._tcp_connect_port > 0:
+            # tell device side to exit connection loop, and remove the forwarding
+            # connection
+            self.executeOnDevice(PowerTest.REQUEST_EXIT, reportErrors = False)
+            self.executeLocal("adb forward --remove tcp:%d" % self._tcp_connect_port)
+        self._tcp_connect_port = 0
         if self._power_monitor:
             self._power_monitor.Close()
             self._power_monitor = None
 
-    def _send(self, msg, report_errors=True):
+    def _send(self, msg, report_errors = True):
         """Connect to the device, send the given command, and then disconnect"""
-        if PowerTest.PORT == 0:
+        if self._tcp_connect_port == 0:
             # on first attempt to send a command, connect to device via any open port number,
             # forwarding that port to a local socket on the device via adb
             logging.debug("Seeking port for communication...")
             # discover an open port
             dummysocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
             dummysocket.bind(("localhost", 0))
-            (_, PowerTest.PORT) = dummysocket.getsockname()
+            (_, self._tcp_connect_port) = dummysocket.getsockname()
             dummysocket.close()
-            assert(PowerTest.PORT > 0)
+            assert(self._tcp_connect_port > 0)
+
             status = self.executeLocal("adb forward tcp:%d localabstract:%s" %
-                    (PowerTest.PORT, PowerTest.DOMAIN_NAME))
-            if report_errors:
-                self.reportErrorIf(status != 0, msg="Unable to forward requests to client over adb")
-            logging.info("Forwarding requests over local port %d" % PowerTest.PORT)
+                                       (self._tcp_connect_port, PowerTest.DOMAIN_NAME))
+            # If the status !=0, then the host machine is unable to
+            # forward requests to client over adb. Ending the test and logging error message
+            # to the console on the host.
+            self.endTestIfLostConnection(
+                status != 0,
+                "Unable to forward requests to client over adb")
+            logging.info("Forwarding requests over local port %d",
+                         self._tcp_connect_port)
 
         link = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
         try:
             logging.debug("Connecting to device...")
-            link.connect (("localhost", PowerTest.PORT))
+            link.connect(("localhost", self._tcp_connect_port))
             logging.debug("Connected.")
+        except socket.error as serr:
+            print "Socket connection error: ", serr
+            print "Finalizing and exiting the test"
+            self.endTestIfLostConnection(
+                report_errors,
+                "Unable to communicate with device: connection refused")
         except:
-            if report_errors:
-                self.reportErrorIf(True, msg="Unable to communicate with device: connection refused")
-        logging.debug("Sending '%s'" % msg)
+            print "Non socket-related exception at this block in _send(); re-raising now."
+            raise
+        logging.debug("Sending '%s'", msg)
         link.sendall(msg)
         logging.debug("Getting response...")
         response = link.recv(4096)
-        logging.debug("Got response '%s'" % response)
+        logging.debug("Got response '%s'", response)
         link.close()
         return response
 
     def queryDevice(self, query):
         """Post a yes/no query to the device, return True upon successful query, False otherwise"""
-        logging.info("Querying device with '%s'" % query)
+        logging.info("Querying device with '%s'", query)
         return self._send(query) == "OK"
 
     # TODO: abstract device communication (and string commands) into its own class
-    def executeOnDevice(self, cmd , reportErrors=True):
+    def executeOnDevice(self, cmd, reportErrors = True):
         """Execute a (string) command on the remote device"""
-        return self._send(cmd , reportErrors)
+        return self._send(cmd, reportErrors)
 
-    def executeLocal(self, cmd, check_status=True):
+    def executeLocal(self, cmd, check_status = True):
         """execute a shell command locally (on the host)"""
         from subprocess import call
-        status = call(cmd.split(' '))
+        status = call(cmd.split(" "))
         if status != 0 and check_status:
-            logging.error("Failed to execute \"%s\"" % cmd)
+            logging.error("Failed to execute \"%s\"", cmd)
         else:
-            logging.debug("Executed \"%s\"" % cmd)
+            logging.debug("Executed \"%s\"", cmd)
         return status
 
-    def reportErrorIf(self, condition, msg):
+    def reportErrorRaiseExceptionIf(self, condition, msg):
         """Report an error condition to the device if condition is True.
-        Will raise an exception on the device if condition is True"""
+        Will raise an exception on the device if condition is True.
+        Args:
+            condition: If true, this reports error
+            msg: Message related to exception
+        Raises:
+            A PowerTestException encapsulating the message provided in msg
+        """
         if condition:
             try:
                 logging.error("Exiting on error: %s" % msg)
-                self.executeOnDevice(PowerTest.REQUEST_RAISE % (self._current_test, msg), False)
+                self.executeOnDevice(PowerTest.REQUEST_RAISE % (self._current_test, msg),
+                                     reportErrors = True)
             except:
-
-                logging.error("Unable to communicate with device to report error: %s" % msg)
+                logging.error("Unable to communicate with device to report "
+                              "error: %s" % msg)
                 self.finalize()
                 sys.exit(msg)
-            raise Exception(msg)
+            raise PowerTestException(msg)
 
-    def setUsbEnabled(self, enabled, verbose=True):
+    def endTestIfLostConnection(self, lost_connection, error_message):
+        """
+        This function ends the test if lost_connection was true,
+        which indicates that the connection to the device was lost.
+        Args:
+            lost_connection: boolean variable, if True it indicates that
+                connection to device was lost and the test must be terminated.
+            error_message: String to print to the host console before exiting the test
+                (if lost_connection is True)
+        Returns:
+            None.
+        """
+        if lost_connection:
+            logging.error(error_message)
+            self.finalize()
+            sys.exit(error_message)
+
+    def setUsbEnabled(self, enabled, verbose = True):
         if enabled:
             val = 1
         else:
@@ -193,6 +298,7 @@
 
         # Sometimes command won't go through first time, particularly if immediately after a data
         # collection, so allow for retries
+        # TODO: Move this retry mechanism to the power monitor driver.
         status = self._power_monitor.GetStatus()
         while status is None and tries < 5:
             tries += 1
@@ -203,59 +309,230 @@
             status = self._power_monitor.GetStatus()
 
         if enabled:
-            if verbose: print("...USB enabled, waiting for device")
-            self.executeLocal ("adb wait-for-device")
-            if verbose: print("...device online")
+            if verbose:
+                print("...USB enabled, waiting for device")
+            self.executeLocal("adb wait-for-device")
+            if verbose:
+                print("...device online")
         else:
-            if verbose: logging.info("...USB disabled")
+            if verbose:
+                logging.info("...USB disabled")
         # re-establish port forwarding
-        if enabled and PowerTest.PORT > 0:
+        if enabled and self._tcp_connect_port > 0:
             status = self.executeLocal("adb forward tcp:%d localabstract:%s" %
-                                       (PowerTest.PORT, PowerTest.DOMAIN_NAME))
-            self.reportErrorIf(status != 0, msg="Unable to forward requests to client over adb")
+                                       (self._tcp_connect_port, PowerTest.DOMAIN_NAME))
+            self.reportErrorRaiseExceptionIf(status != 0, msg = "Unable to forward requests to client over adb")
 
-    def waitForScreenOff(self):
-        # disconnect of USB will cause screen to go on, so must wait (1 second more than screen off
-        # timeout)
-        if USE_STRICT_DELAY:
-            time.sleep(DELAY_SCREEN_OFF)
-            return
+    def computeBaselineState(self, measurements):
+        """
+        Args:
+            measurements: List of floats containing ampere draw measurements
+                taken from the monsoon power monitor.
+                Must be atleast 100 measurements long
+        Returns:
+            A tuple (isBaseline, mean_current) where isBaseline is a
+            boolean that is True only if the baseline state for the phone is
+            detected. mean_current is an estimate of the average baseline
+            current for the device, which is valid only if baseline state is
+            detected (if not, it is set to -1).
+        """
 
-        # need at least 100 sequential clean low-power measurements to know screen is off
-        THRESHOLD_COUNT_LOW_POWER = 100
-        CURRENT_LOW_POWER_THRESHOLD = 0.060  # Amps
-        TIMEOUT_SCREEN_OFF = 30 # this many tries at most
-        count_good = 0
-        tries = 0
-        print("Waiting for screen off and application processor in suspend mode...")
-        while count_good < THRESHOLD_COUNT_LOW_POWER:
-            measurements = self.collectMeasurements(THRESHOLD_COUNT_LOW_POWER,
-                                                      PowerTest.RATE_NOMINAL,
-                                                      ensure_screen_off=False,
-                                                      verbose=False)
-            count_good = len([m for m in measurements
-                               if m < CURRENT_LOW_POWER_THRESHOLD])
-            tries += 1
-            if count_good < THRESHOLD_COUNT_LOW_POWER and measurements:
-                print("Current usage: %.2f mAmps. Device is probably not in suspend mode.   Waiting..." %
-                      (1000.0*(sum(measurements)/len(measurements))))
-            if tries >= TIMEOUT_SCREEN_OFF:
-                # TODO: dump the state of sensor service to identify if there are features using sensors
-                self.reportErrorIf(tries>=TIMEOUT_SCREEN_OFF,
-                    msg="Unable to determine application processor suspend mode status.")
+        # Looks at the measurements to see if it is in baseline state
+        if len(measurements) < 100:
+            print(
+                "Need at least 100 measurements to determine if baseline state has"
+                " been reached")
+            return (False, -1)
+
+        # Assumption: At baseline state, the power profile is Gaussian distributed
+        # with low-variance around the mean current draw.
+        # Ideally we should find the mode from a histogram bin to find an estimated mean.
+        # Assuming here that the median is very close to this value; later we check that the
+        # variance of the samples is low enough to validate baseline.
+        sorted_measurements = sorted(measurements)
+        number_measurements = len(measurements)
+        if not number_measurements % 2:
+            median_measurement = (sorted_measurements[(number_measurements - 1) / 2] +
+                                  sorted_measurements[(number_measurements + 1) / 2]) / 2
+        else:
+            median_measurement = sorted_measurements[number_measurements / 2]
+
+        # Assume that at baseline state, a large fraction of power measurements
+        # are within +/- EXPECTED_AMPS_VARIATION_HALF_RANGE milliAmperes of
+        # the average baseline current. Find all such measurements in the
+        # sorted measurement vector.
+        left_index = (
+            bisect_left(
+                sorted_measurements,
+                median_measurement -
+                PowerTest.EXPECTED_AMPS_VARIATION_HALF_RANGE))
+        right_index = (
+            bisect_left(
+                sorted_measurements,
+                median_measurement +
+                PowerTest.EXPECTED_AMPS_VARIATION_HALF_RANGE))
+
+        average_baseline_amps = scipy.mean(
+            sorted_measurements[left_index: (right_index - 1)])
+
+        detected_baseline = True
+        # We enforce that a fraction of more than 'THRESHOLD_BASELINE_SAMPLES_FRACTION'
+        # of samples must be within +/- EXPECTED_AMPS_VARIATION_HALF_RANGE
+        # milliAmperes of the mean baseline current, which we have estimated as
+        # the median.
+        if ((right_index - left_index) < PowerTest.THRESHOLD_BASELINE_SAMPLES_FRACTION * len(
+                measurements)):
+            detected_baseline = False
+
+        # We check for the maximum limit of the expected baseline
+        if median_measurement > self._max_baseline_amps:
+            detected_baseline = False
+        if average_baseline_amps < 0:
+            print PowerTest.NEGATIVE_AMPERE_ERROR_MESSAGE
+            detected_baseline = False
+
+        print("%s baseline state" % ("Could detect" if detected_baseline else "Could NOT detect"))
+        print(
+            "median amps = %f, avg amps = %f, fraction of good samples = %f" %
+            (median_measurement, average_baseline_amps,
+             float(right_index - left_index) / len(measurements)))
+        if PowerTest.ENABLE_PLOTTING:
+            plt.plot(measurements)
+            plt.show()
+            print("To continue test, please close the plot window manually.")
+        return (detected_baseline, average_baseline_amps)
+
+    def isApInSuspendState(self, measurements_amps, nominal_max_amps, test_percentile):
+        """
+        This function detects AP suspend and display off state of phone
+        after a sensor has been registered.
+
+        Because the power profile can be very different between sensors and
+        even across builds, it is difficult to specify a tight threshold for
+        mean current draw or mandate that the power measurements must have low
+        variance. We use a criteria that allows for a certain fraction of
+        peaks in power spectrum and checks that test_percentile fraction of
+        measurements must be below the specified value nominal_max_amps
+        Args:
+            measurements_amps: amperes draw measurements from power monitor
+            test_percentile: the fraction of measurements we require to be below
+                             a specified amps value
+            nominal_max_amps: the specified value of the max current draw
+        Returns:
+            returns a boolean which is True if and only if the AP suspend and
+            display off state is detected
+        """
+        count_good = len([m for m in measurements_amps if m < nominal_max_amps])
+        count_negative = len([m for m in measurements_amps if m < 0])
+        if count_negative > 0:
+            print PowerTest.NEGATIVE_AMPERE_ERROR_MESSAGE
+            return False;
+        return count_good > test_percentile * len(measurements_amps)
+
+    def getBaselineState(self):
+        """This function first disables all sensors, then collects measurements
+        through the power monitor and continuously evaluates if baseline state
+        is reached. Once baseline state is detected, it returns a tuple with
+        status information. If baseline is not detected in a preset maximum
+        number of trials, it returns as well.
+
+        Returns:
+            Returns a tuple (isBaseline, mean_current) where isBaseline is a
+            boolean that is True only if the baseline state for the phone is
+            detected. mean_current is an estimate of the average baseline current
+            for the device, which is valid only if baseline state is detected
+            (if not, it is set to -1)
+        """
+        self.setPowerOn("ALL", False)
+        self.setUsbEnabled(False)
+        print("Waiting %d seconds for baseline state" % DELAY_SCREEN_OFF)
+        time.sleep(DELAY_SCREEN_OFF)
+
+        MEASUREMENT_DURATION_SECONDS_BASELINE_DETECTION = 5  # seconds
+        NUMBER_MEASUREMENTS_BASELINE_DETECTION = (
+            PowerTest.RATE_NOMINAL *
+            MEASUREMENT_DURATION_SECONDS_BASELINE_DETECTION)
+        NUMBER_MEASUREMENTS_BASELINE_VERIFICATION = (
+            NUMBER_MEASUREMENTS_BASELINE_DETECTION * 5)
+        MAX_TRIALS = 50
+
+        collected_baseline_measurements = False
+
+        for tries in xrange(MAX_TRIALS):
+            print("Trial number %d of %d..." % (tries, MAX_TRIALS))
+            measurements = self.collectMeasurements(
+                NUMBER_MEASUREMENTS_BASELINE_DETECTION, PowerTest.RATE_NOMINAL,
+                verbose = False)
+            if self.computeBaselineState(measurements)[0] is True:
+                collected_baseline_measurements = True
                 break
-        if DELAY_SCREEN_OFF:
-            # add additional delay time if necessary
-            time.sleep(DELAY_SCREEN_OFF)
-        print("...Screen off and device in suspend mode.")
 
-    def collectMeasurements(self, measurementCount, rate , ensure_screen_off=True, verbose=True,
-                             plot_data = False):
-        assert(measurementCount > 0)
-        decimate_by = self._native_hz / rate  or 1
-        if ensure_screen_off:
-            self.waitForScreenOff()
-            print ("Taking measurements...")
+        if collected_baseline_measurements:
+            print("Verifying baseline state over a longer interval "
+                  "in order to double check baseline state")
+            measurements = self.collectMeasurements(
+                NUMBER_MEASUREMENTS_BASELINE_VERIFICATION, PowerTest.RATE_NOMINAL,
+                verbose = False)
+            self.reportErrorRaiseExceptionIf(
+                not measurements, "No background measurements could be taken")
+            retval = self.computeBaselineState(measurements)
+            if retval[0]:
+                print("Verified baseline.")
+                if measurements and LOG_DATA_TO_FILE:
+                    with open("/tmp/cts-power-tests-background-data.log", "w") as f:
+                        for m in measurements:
+                            f.write("%.4f\n" % m)
+            return retval
+        else:
+            return (False, -1)
+
+    def waitForApSuspendMode(self):
+        """This function repeatedly collects measurements until AP suspend and display off
+        mode is detected. After a maximum number of trials, if this state is not reached, it
+        raises an error.
+        Returns:
+            boolean which is True if device was detected to be in suspend state
+        Raises:
+            Power monitor-related exception
+        """
+        print("waitForApSuspendMode(): Sleeping for %d seconds" % DELAY_SCREEN_OFF)
+        time.sleep(DELAY_SCREEN_OFF)
+
+        NUMBER_MEASUREMENTS = 200
+        # Maximum trials for which to collect measurements to get to Ap suspend
+        # state
+        MAX_TRIALS = 50
+
+        got_to_suspend_state = False
+        for count in xrange(MAX_TRIALS):
+            print ("waitForApSuspendMode(): Trial %d of %d" % (count, MAX_TRIALS))
+            measurements = self.collectMeasurements(NUMBER_MEASUREMENTS,
+                                                    PowerTest.RATE_NOMINAL,
+                                                    verbose = False)
+            if self.isApInSuspendState(
+                    measurements, PowerTest.MAX_PERCENTILE_AP_SCREEN_OFF_AMPS,
+                    PowerTest.PERCENTILE_MAX_AP_SCREEN_OFF):
+                got_to_suspend_state = True
+                break
+        self.reportErrorRaiseExceptionIf(
+            got_to_suspend_state is False,
+            msg = "Unable to determine application processor suspend mode status.")
+        print("Got to AP suspend state")
+        return got_to_suspend_state
+
+    def collectMeasurements(self, measurementCount, rate, verbose = True):
+        """Args:
+            measurementCount: Number of measurements to collect from the power
+                              monitor
+            rate: The integer frequency in Hertz at which to collect measurements from
+                  the power monitor
+        Returns:
+            A list containing measurements from the power monitor; that has the
+            requested count of the number of measurements at the specified rate
+        """
+        assert (measurementCount > 0)
+        decimate_by = self._native_hz / rate or 1
+
         self._power_monitor.StartDataCollection()
         sub_measurements = []
         measurements = []
@@ -273,47 +550,70 @@
                     tries = 0
                     sub_measurements.extend(additional)
                     while len(sub_measurements) >= decimate_by:
-                        sub_avg = sum(sub_measurements) / len(sub_measurements)
+                        sub_avg = sum(sub_measurements[0:decimate_by]) / decimate_by
                         measurements.append(sub_avg)
                         sub_measurements = sub_measurements[decimate_by:]
                         if verbose:
+                            # "\33[1A\33[2K" is a special Linux console control
+                            # sequence for moving to the previous line, and
+                            # erasing it; and reprinting new text on that
+                            # erased line.
                             sys.stdout.write("\33[1A\33[2K")
-                            print ("MEASURED[%d]: %f" % (len(measurements),measurements[-1]))
+                            print ("MEASURED[%d]: %f" % (len(measurements), measurements[-1]))
         finally:
             self._power_monitor.StopDataCollection()
 
-        self.reportErrorIf(measurementCount > len(measurements),
-                            "Unable to collect all requested measurements")
+        self.reportErrorRaiseExceptionIf(measurementCount > len(measurements),
+                           "Unable to collect all requested measurements")
         return measurements
 
-    def request_user_acknowledgment(self, msg):
+    def requestUserAcknowledgment(self, msg):
         """Post message to user on screen and wait for acknowledgment"""
         response = self.executeOnDevice(PowerTest.REQUEST_USER_RESPONSE % msg)
-        self.reportErrorIf(response != "OK", "Unable to request user acknowledgment")
+        self.reportErrorRaiseExceptionIf(
+            response != "OK", "Unable to request user acknowledgment")
 
-    def setTestResult (self, testname, condition, msg):
-        if condition is False:
-            val = "FAIL"
-        elif condition is True:
-            val = "PASS"
-        else:
-            val = condition
-        print ("Test %s : %s" % (testname, val))
-        response = self.executeOnDevice(PowerTest.REQUEST_SET_TEST_RESULT % (testname, val, msg))
-        self.reportErrorIf(response != "OK", "Unable to send test status to Verifier")
+    def setTestResult(self, test_name, test_result, test_message):
+        """
+        Reports the result of a test to the device
+        Args:
+            test_name: name of the test
+            test_result: Boolean result of the test (True means Pass)
+            test_message: Relevant message
+        """
+        print ("Test %s : %s" % (test_name, test_result))
+
+        response = (
+            self.executeOnDevice(
+                PowerTest.REQUEST_SET_TEST_RESULT %
+                (test_name, test_result, test_message)))
+        self.reportErrorRaiseExceptionIf(
+            response != "OK", "Unable to send test status to Verifier")
 
     def setPowerOn(self, sensor, powered_on):
         response = self.executeOnDevice(PowerTest.REQUEST_SENSOR_SWITCH %
-                                        ({True:"ON", False:"OFF"}[powered_on], sensor))
-        self.reportErrorIf(response == "ERR", "Unable to set sensor %s state" % sensor)
-        logging.info("Set %s %s" % (sensor, {True:"ON", False:"OFF"}[powered_on]))
+            (("ON" if powered_on else "OFF"), sensor))
+        self.reportErrorRaiseExceptionIf(
+            response == "ERR", "Unable to set sensor %s state" % sensor)
+        logging.info("Set %s %s", sensor, ("ON" if powered_on else "OFF"))
         return response
 
-    def runPowerTest(self, sensor, max_power_allowed, user_request = None):
-        if not signal_abort.empty():
-            sys.exit( signal_abort.get() )
-        self._current_test = "%s_Power_Test_While_%s" % (sensor,
-                                    {True:"Under_Motion", False:"Still"}[user_request is not None])
+    def runSensorPowerTest(
+            self, sensor, max_amperes_allowed, baseline_amps, user_request = None):
+        """
+        Runs power test for a specific sensor; i.e. measures the amperes draw
+        of the phone using monsoon, with the specified sensor mregistered
+        and the phone in suspend state; and verifies that the incremental
+        consumed amperes is within expected bounds.
+        Args:
+            sensor: The specified sensor for which to run the power test
+            max_amperes_allowed: Maximum ampere draw of the device with the
+                    sensor registered and device in suspend state
+            baseline_amps: The power draw of the device when it is in baseline
+                    state (no sensors registered, display off, AP asleep)
+        """
+        self._current_test = ("%s_Power_Test_While_%s" % (
+            sensor, ("Under_Motion" if user_request is not None else "Still")))
         try:
             print ("\n\n---------------------------------")
             if user_request is not None:
@@ -321,248 +621,299 @@
             else:
                 print ("Running power test on %s while device is still." % sensor)
             print ("---------------------------------")
-            response = self.executeOnDevice(PowerTest.REQUEST_SENSOR_AVAILABILITY % sensor)
+            response = self.executeOnDevice(
+                PowerTest.REQUEST_SENSOR_AVAILABILITY % sensor)
             if response == "UNAVAILABLE":
-                self.setTestResult(self._current_test, condition="SKIPPED",
-                    msg="Sensor %s not available on this platform"%sensor)
+                self.setTestResult(
+                    self._current_test, test_result = "SKIPPED",
+                    test_message = "Sensor %s not available on this platform" % sensor)
             self.setPowerOn("ALL", False)
             if response == "UNAVAILABLE":
-                self.setTestResult(self._current_test, condition="SKIPPED",
-                                   msg="Sensor %s not available on this device"%sensor)
+                self.setTestResult(
+                    self._current_test, test_result = "SKIPPED",
+                    test_message = "Sensor %s not available on this device" % sensor)
                 return
-
-            self.reportErrorIf(response != "OK", "Unable to set all sensor off")
-            if not signal_abort.empty():
-                sys.exit( signal_abort.get() )
+            self.reportErrorRaiseExceptionIf(response != "OK", "Unable to set all sensor off")
             self.executeOnDevice(PowerTest.REQUEST_SCREEN_OFF)
             self.setUsbEnabled(False)
-            print("Collecting background measurements...")
-            measurements = self.collectMeasurements( PowerTest.SAMPLE_COUNT_NOMINAL,
-                                                     PowerTest.RATE_NOMINAL,
-                                                     plot_data = True)
-            if measurements and LOG_DATA_TO_FILE:
-                with open( "/tmp/cts-power-tests-%s-%s-background-data.log"%(sensor,
-                   {True:"Under_Motion", False:"Still"}[user_request is not None] ),'w') as f:
-                    for m in measurements:
-                        f.write( "%.4f\n"%m)
-            self.reportErrorIf(not measurements, "No background measurements could be taken")
-            backgnd = sum(measurements) / len(measurements)
             self.setUsbEnabled(True)
             self.setPowerOn(sensor, True)
             if user_request is not None:
                 print("===========================================\n" +
                       "==> Please follow the instructions presented on the device\n" +
-                      "==========================================="
-                     )
-                self.request_user_acknowledgment(user_request)
+                      "===========================================")
+                self.requestUserAcknowledgment(user_request)
             self.executeOnDevice(PowerTest.REQUEST_SCREEN_OFF)
             self.setUsbEnabled(False)
-            self.reportErrorIf(response != "OK", "Unable to set sensor %s ON" % sensor)
+            self.reportErrorRaiseExceptionIf(
+                response != "OK", "Unable to set sensor %s ON" % sensor)
+
+            self.waitForApSuspendMode()
             print ("Collecting sensor %s measurements" % sensor)
             measurements = self.collectMeasurements(PowerTest.SAMPLE_COUNT_NOMINAL,
                                                     PowerTest.RATE_NOMINAL)
 
             if measurements and LOG_DATA_TO_FILE:
-                with open( "/tmp/cts-power-tests-%s-%s-sensor-data.log"%(sensor,
-                   {True:"Under_Motion", False:"Still"}[user_request is not None] ),'w') as f:
+                with open("/tmp/cts-power-tests-%s-%s-sensor-data.log" % (sensor,
+                    ("Under_Motion" if user_request is not None else "Still")), "w") as f:
                     for m in measurements:
-                        f.write( "%.4f\n"%m)
+                        f.write("%.4f\n" % m)
                     self.setUsbEnabled(True, verbose = False)
                     print("Saving raw data files to device...")
                     self.executeLocal("adb shell mkdir -p %s" % self._external_storage, False)
                     self.executeLocal("adb push %s %s/." % (f.name, self._external_storage))
                     self.setUsbEnabled(False, verbose = False)
-            self.reportErrorIf(not measurements, "No measurements could be taken for %s" % sensor)
+            self.reportErrorRaiseExceptionIf(
+                not measurements, "No measurements could be taken for %s" % sensor)
             avg = sum(measurements) / len(measurements)
-            squared = [(m-avg)*(m-avg) for m in measurements]
+            squared = [(m - avg) * (m - avg) for m in measurements]
 
-            import math
-            stddev = math.sqrt(sum(squared)/len(squared))
-            current_diff = avg - backgnd
+            stddev = math.sqrt(sum(squared) / len(squared))
+            current_diff = avg - baseline_amps
             self.setUsbEnabled(True)
             max_power = max(measurements) - avg
-            if current_diff <= max_power_allowed:
+            if current_diff <= max_amperes_allowed:
                 # TODO: fail the test of background > current
-                message = ("Draw is within limits. Current:%f Background:%f   Measured: %f Stddev: %f  Peak: %f")%\
-                             ( current_diff*1000.0, backgnd*1000.0, avg*1000.0, stddev*1000.0, max_power*1000.0)
+                message = (
+                              "Draw is within limits. Sensor delta:%f mAmp   Baseline:%f "
+                              "mAmp   Sensor: %f mAmp  Stddev : %f mAmp  Peak: %f mAmp") % (
+                              current_diff * 1000.0, baseline_amps * 1000.0, avg * 1000.0,
+                              stddev * 1000.0, max_power * 1000.0)
             else:
-                message = ("Draw is too high. Current:%f Background:%f   Measured: %f Stddev: %f  Peak: %f")%\
-                             ( current_diff*1000.0, backgnd*1000.0, avg*1000.0, stddev*1000.0, max_power*1000.0)
-            self.setTestResult( testname = self._current_test,
-                                condition = current_diff <= max_power_allowed,
-                                msg = message)
-            print("Result: "+message)
+                message = (
+                              "Draw is too high. Current:%f Background:%f   Measured: %f "
+                              "Stddev: %f  Peak: %f") % (
+                              current_diff * 1000.0, baseline_amps * 1000.0, avg * 1000.0,
+                              stddev * 1000.0, max_power * 1000.0)
+            self.setTestResult(
+                self._current_test,
+                ("PASS" if (current_diff <= max_amperes_allowed) else "FAIL"),
+                message)
+            print("Result: " + message)
         except:
-            import traceback
             traceback.print_exc()
-            self.setTestResult(self._current_test, condition="FAIL",
-                               msg="Exception occurred during run of test.")
-
+            self.setTestResult(self._current_test, test_result = "FAIL",
+                               test_message = "Exception occurred during run of test.")
+            raise
 
     @staticmethod
-    def run_tests():
+    def runTests(max_baseline_amps):
         testrunner = None
         try:
-            GENERIC_MOTION_REQUEST = "\n===> Please press Next and when the screen is off, keep " + \
-                "the device under motion with only tiny, slow movements until the screen turns " + \
-                "on again.\nPlease refrain from interacting with the screen or pressing any side " + \
-                "buttons while measurements are taken."
-            USER_STEPS_REQUEST = "\n===> Please press Next and when the screen is off, then move " + \
-                "the device to simulate step motion until the screen turns on again.\nPlease " + \
-                "refrain from interacting with the screen or pressing any side buttons while " + \
-                "measurements are taken."
-            testrunner = PowerTest()
-            testrunner.executeOnDevice(PowerTest.REQUEST_SHOW_MESSAGE % "Connected.  Running tests...")
-            testrunner.runPowerTest("SIGNIFICANT_MOTION", PowerTest.MAX_SIGMO_POWER, user_request = GENERIC_MOTION_REQUEST)
-            testrunner.runPowerTest("STEP_DETECTOR", PowerTest.MAX_STEP_DETECTOR_POWER, user_request = USER_STEPS_REQUEST)
-            testrunner.runPowerTest("STEP_COUNTER", PowerTest.MAX_STEP_COUNTER_POWER, user_request = USER_STEPS_REQUEST)
-            testrunner.runPowerTest("ACCELEROMETER", PowerTest.MAX_ACCEL_POWER, user_request = GENERIC_MOTION_REQUEST)
-            testrunner.runPowerTest("MAGNETIC_FIELD", PowerTest.MAX_MAG_POWER, user_request = GENERIC_MOTION_REQUEST)
-            testrunner.runPowerTest("GYROSCOPE", PowerTest.MAX_GYRO_POWER, user_request = GENERIC_MOTION_REQUEST)
-            testrunner.runPowerTest("ACCELEROMETER", PowerTest.MAX_ACCEL_POWER, user_request = None)
-            testrunner.runPowerTest("MAGNETIC_FIELD", PowerTest.MAX_MAG_POWER, user_request = None)
-            testrunner.runPowerTest("GYROSCOPE", PowerTest.MAX_GYRO_POWER, user_request = None)
-            testrunner.runPowerTest("SIGNIFICANT_MOTION", PowerTest.MAX_SIGMO_POWER, user_request = None)
-            testrunner.runPowerTest("STEP_DETECTOR", PowerTest.MAX_STEP_DETECTOR_POWER, user_request = None)
-            testrunner.runPowerTest("STEP_COUNTER", PowerTest.MAX_STEP_COUNTER_POWER, user_request = None)
+            GENERIC_MOTION_REQUEST = ("\n===> Please press Next and when the "
+                "screen is off, keep the device under motion with only tiny, "
+                "slow movements until the screen turns on again.\nPlease "
+                "refrain from interacting with the screen or pressing any side "
+                "buttons while measurements are taken.")
+            USER_STEPS_REQUEST = ("\n===> Please press Next and when the "
+                "screen is off, then move the device to simulate step motion "
+                "until the screen turns on again.\nPlease refrain from "
+                "interacting with the screen or pressing any side buttons "
+                "while measurements are taken.")
+            testrunner = PowerTest(max_baseline_amps)
+            testrunner.executeOnDevice(
+                PowerTest.REQUEST_SHOW_MESSAGE % "Connected.  Running tests...")
+            is_baseline_success, baseline_amps = testrunner.getBaselineState()
+
+            if is_baseline_success:
+                testrunner.setUsbEnabled(True)
+                # TODO: Enable testing a single sensor
+                testrunner.runSensorPowerTest(
+                    "SIGNIFICANT_MOTION", PowerTest.MAX_SIGMO_AMPS, baseline_amps,
+                    user_request = GENERIC_MOTION_REQUEST)
+                testrunner.runSensorPowerTest(
+                    "STEP_DETECTOR", PowerTest.MAX_STEP_DETECTOR_AMPS, baseline_amps,
+                    user_request = USER_STEPS_REQUEST)
+                testrunner.runSensorPowerTest(
+                    "STEP_COUNTER", PowerTest.MAX_STEP_COUNTER_AMPS, baseline_amps,
+                    user_request = USER_STEPS_REQUEST)
+                testrunner.runSensorPowerTest(
+                    "ACCELEROMETER", PowerTest.MAX_ACCEL_AMPS, baseline_amps,
+                    user_request = GENERIC_MOTION_REQUEST)
+                testrunner.runSensorPowerTest(
+                    "MAGNETIC_FIELD", PowerTest.MAX_MAG_AMPS, baseline_amps,
+                    user_request = GENERIC_MOTION_REQUEST)
+                testrunner.runSensorPowerTest(
+                    "GYROSCOPE", PowerTest.MAX_GYRO_AMPS, baseline_amps,
+                    user_request = GENERIC_MOTION_REQUEST)
+                testrunner.runSensorPowerTest(
+                    "ACCELEROMETER", PowerTest.MAX_ACCEL_AMPS, baseline_amps,
+                    user_request = None)
+                testrunner.runSensorPowerTest(
+                    "MAGNETIC_FIELD", PowerTest.MAX_MAG_AMPS, baseline_amps,
+                    user_request = None)
+                testrunner.runSensorPowerTest(
+                    "GYROSCOPE", PowerTest.MAX_GYRO_AMPS, baseline_amps,
+                    user_request = None)
+                testrunner.runSensorPowerTest(
+                    "SIGNIFICANT_MOTION", PowerTest.MAX_SIGMO_AMPS, baseline_amps,
+                    user_request = None)
+                testrunner.runSensorPowerTest(
+                    "STEP_DETECTOR", PowerTest.MAX_STEP_DETECTOR_AMPS, baseline_amps,
+                    user_request = None)
+                testrunner.runSensorPowerTest(
+                    "STEP_COUNTER", PowerTest.MAX_STEP_COUNTER_AMPS, baseline_amps,
+                    user_request = None)
+            else:
+                print("Could not get to baseline state. This is either because "
+                      "in several trials, the monitor could not measure a set "
+                      "of power measurements that had the specified low "
+                      "variance or the mean measurements were below the "
+                      "expected value. None of the sensor power measurement "
+                      " tests were performed due to not being able to detect "
+                      "baseline state. Please re-run the power tests.")
+        except KeyboardInterrupt:
+            print "Keyboard interrupt from user."
+            raise
         except:
             import traceback
             traceback.print_exc()
         finally:
-            signal_exit_q.put(0) # anything will signal thread to terminate
             logging.info("TESTS COMPLETE")
             if testrunner:
                 try:
                     testrunner.finalize()
                 except socket.error:
-                    sys.exit("============================\nUnable to connect to device under " + \
-                             "test. Make sure the device is connected via the usb pass-through, " + \
-                             "the CtsVerifier app is running the SensorPowerTest on the device, " + \
-                             "and USB pass-through is enabled.\n===========================")
-
+                    sys.exit(
+                        "===================================================\n"
+                        "Unable to connect to device under test. Make sure \n"
+                        "the device is connected via the usb pass-through, \n"
+                        "the CtsVerifier app is running the SensorPowerTest on \n"
+                        "the device, and USB pass-through is enabled.\n"
+                        "===================================================")
 
 def main(argv):
-  """ Simple command-line interface for a power test application."""
-  useful_flags = ["voltage", "status", "usbpassthrough",
-                  "samples", "current", "log", "power_monitor"]
-  if not [f for f in useful_flags if FLAGS.get(f, None) is not None]:
-    print __doc__.strip()
-    print FLAGS.MainModuleHelp()
-    return
+    """ Simple command-line interface for a power test application."""
+    useful_flags = ["voltage", "status", "usbpassthrough",
+                    "samples", "current", "log", "power_monitor"]
+    if not [f for f in useful_flags if FLAGS.get(f, None) is not None]:
+        print __doc__.strip()
+        print FLAGS.MainModuleHelp()
+        return
 
-  if FLAGS.avg and FLAGS.avg < 0:
-    loggign.error("--avg must be greater than 0")
-    return
+    if FLAGS.avg and FLAGS.avg < 0:
+        logging.error("--avg must be greater than 0")
+        return
 
-  if FLAGS.voltage is not None:
-    if FLAGS.voltage > 5.5:
-        print("!!WARNING: Voltage higher than typical values!!!")
-    try:
-        response = raw_input("Voltage of %.3f requested.  Confirm this is correct (Y/N)"%FLAGS.voltage)
-        if response.upper() != "Y":
-            sys.exit("Aborting")
-    except:
-        sys.exit("Aborting.")
+    if FLAGS.voltage is not None:
+        if FLAGS.voltage > 5.5:
+            print("!!WARNING: Voltage higher than typical values!!!")
+        try:
+            response = raw_input(
+                "Voltage of %.3f requested.  Confirm this is correct (Y/N)" %
+                FLAGS.voltage)
+            if response.upper() != "Y":
+                sys.exit("Aborting")
+        except:
+            sys.exit("Aborting.")
 
-  if not FLAGS.power_monitor:
-      sys.exit("You must specify a '--power_monitor' option to specify which power monitor type " + \
-               "you are using.\nOne of:\n  \n  ".join(available_monitors))
-  power_monitors = do_import('power_monitors.%s' % FLAGS.power_monitor)
-  try:
-      mon = power_monitors.Power_Monitor(device=FLAGS.device)
-  except:
-      import traceback
-      traceback.print_exc()
-      sys.exit("No power monitors found")
-
-  if FLAGS.voltage is not None:
-
-    if FLAGS.ramp is not None:
-      mon.RampVoltage(mon.start_voltage, FLAGS.voltage)
-    else:
-      mon.SetVoltage(FLAGS.voltage)
-
-  if FLAGS.current is not None:
-    mon.SetMaxCurrent(FLAGS.current)
-
-  if FLAGS.status:
-    items = sorted(mon.GetStatus().items())
-    print "\n".join(["%s: %s" % item for item in items])
-
-  if FLAGS.usbpassthrough:
-    if FLAGS.usbpassthrough == 'off':
-      mon.SetUsbPassthrough(0)
-    elif FLAGS.usbpassthrough == 'on':
-      mon.SetUsbPassthrough(1)
-    elif FLAGS.usbpassthrough == 'auto':
-      mon.SetUsbPassthrough(2)
-    else:
-      mon.Close()
-      sys.exit('bad pass-through flag: %s' % FLAGS.usbpassthrough)
-
-  if FLAGS.samples:
-    # Make sure state is normal
-    mon.StopDataCollection()
-    status = mon.GetStatus()
-    native_hz = status["sampleRate"] * 1000
-
-    # Collect and average samples as specified
-    mon.StartDataCollection()
-
-    # In case FLAGS.hz doesn't divide native_hz exactly, use this invariant:
-    # 'offset' = (consumed samples) * FLAGS.hz - (emitted samples) * native_hz
-    # This is the error accumulator in a variation of Bresenham's algorithm.
-    emitted = offset = 0
-    collected = []
-    history_deque = collections.deque()  # past n samples for rolling average
-
-    try:
-      last_flush = time.time()
-      while emitted < FLAGS.samples or FLAGS.samples == -1:
-        # The number of raw samples to consume before emitting the next output
-        need = (native_hz - offset + FLAGS.hz - 1) / FLAGS.hz
-        if need > len(collected):  # still need more input samples
-          samples = mon.CollectData()
-          if not samples: break
-          collected.extend(samples)
-        else:
-          # Have enough data, generate output samples.
-          # Adjust for consuming 'need' input samples.
-          offset += need * FLAGS.hz
-          while offset >= native_hz:  # maybe multiple, if FLAGS.hz > native_hz
-            this_sample = sum(collected[:need]) / need
-
-            if FLAGS.timestamp: print int(time.time()),
-
-            if FLAGS.avg:
-              history_deque.appendleft(this_sample)
-              if len(history_deque) > FLAGS.avg: history_deque.pop()
-              print "%f %f" % (this_sample,
-                               sum(history_deque) / len(history_deque))
-            else:
-              print "%f" % this_sample
-            sys.stdout.flush()
-
-            offset -= native_hz
-            emitted += 1  # adjust for emitting 1 output sample
-          collected = collected[need:]
-          now = time.time()
-          if now - last_flush >= 0.99:  # flush every second
-            sys.stdout.flush()
-            last_flush = now
-    except KeyboardInterrupt:
-      print("interrupted")
-      return 1
-    finally:
-      mon.Close()
-    return 0
-
-  if FLAGS.run:
     if not FLAGS.power_monitor:
-        sys.exit("When running power tests, you must specify which type of power monitor to use" +
-                 " with '--power_monitor <type of power monitor>'")
-    PowerTest.run_tests()
+        sys.exit(
+            "You must specify a '--power_monitor' option to specify which power "
+            "monitor type " +
+            "you are using.\nOne of:\n  \n  ".join(available_monitors))
+    power_monitors = do_import("power_monitors.%s" % FLAGS.power_monitor)
+    try:
+        mon = power_monitors.Power_Monitor(device = FLAGS.device)
+    except:
+        import traceback
 
+        traceback.print_exc()
+        sys.exit("No power monitors found")
+
+    if FLAGS.voltage is not None:
+
+        if FLAGS.ramp is not None:
+            mon.RampVoltage(mon.start_voltage, FLAGS.voltage)
+        else:
+            mon.SetVoltage(FLAGS.voltage)
+
+    if FLAGS.current is not None:
+        mon.SetMaxCurrent(FLAGS.current)
+
+    if FLAGS.status:
+        items = sorted(mon.GetStatus().items())
+        print "\n".join(["%s: %s" % item for item in items])
+
+    if FLAGS.usbpassthrough:
+        if FLAGS.usbpassthrough == "off":
+            mon.SetUsbPassthrough(0)
+        elif FLAGS.usbpassthrough == "on":
+            mon.SetUsbPassthrough(1)
+        elif FLAGS.usbpassthrough == "auto":
+            mon.SetUsbPassthrough(2)
+        else:
+            mon.Close()
+            sys.exit("bad pass-through flag: %s" % FLAGS.usbpassthrough)
+
+    if FLAGS.samples:
+        # Make sure state is normal
+        mon.StopDataCollection()
+        status = mon.GetStatus()
+        native_hz = status["sampleRate"] * 1000
+
+        # Collect and average samples as specified
+        mon.StartDataCollection()
+
+        # In case FLAGS.hz doesn't divide native_hz exactly, use this invariant:
+        # 'offset' = (consumed samples) * FLAGS.hz - (emitted samples) * native_hz
+        # This is the error accumulator in a variation of Bresenham's algorithm.
+        emitted = offset = 0
+        collected = []
+        history_deque = collections.deque()  # past n samples for rolling average
+
+        # TODO: Complicated lines of code below. Refactoring needed
+        try:
+            last_flush = time.time()
+            while emitted < FLAGS.samples or FLAGS.samples == -1:
+                # The number of raw samples to consume before emitting the next output
+                need = (native_hz - offset + FLAGS.hz - 1) / FLAGS.hz
+                if need > len(collected):  # still need more input samples
+                    samples = mon.CollectData()
+                    if not samples: break
+                    collected.extend(samples)
+                else:
+                    # Have enough data, generate output samples.
+                    # Adjust for consuming 'need' input samples.
+                    offset += need * FLAGS.hz
+                    while offset >= native_hz:  # maybe multiple, if FLAGS.hz > native_hz
+                        this_sample = sum(collected[:need]) / need
+
+                        if FLAGS.timestamp: print int(time.time()),
+
+                        if FLAGS.avg:
+                            history_deque.appendleft(this_sample)
+                            if len(history_deque) > FLAGS.avg: history_deque.pop()
+                            print "%f %f" % (this_sample,
+                                             sum(history_deque) / len(history_deque))
+                        else:
+                            print "%f" % this_sample
+                        sys.stdout.flush()
+
+                        offset -= native_hz
+                        emitted += 1  # adjust for emitting 1 output sample
+                    collected = collected[need:]
+                    now = time.time()
+                    if now - last_flush >= 0.99:  # flush every second
+                        sys.stdout.flush()
+                        last_flush = now
+        except KeyboardInterrupt:
+            print("interrupted")
+            return 1
+        finally:
+            mon.Close()
+        return 0
+
+    if FLAGS.run:
+        if not FLAGS.power_monitor:
+            sys.exit(
+                "When running power tests, you must specify which type of power "
+                "monitor to use" +
+                " with '--power_monitor <type of power monitor>'")
+        try:
+            PowerTest.runTests(FLAGS.max_baseline_amps)
+        except KeyboardInterrupt:
+            print "Keyboard interrupt from user"
 
 if __name__ == "__main__":
     flags.DEFINE_boolean("status", None, "Print power meter status")
@@ -581,5 +932,6 @@
     flags.DEFINE_boolean("log", False, "Log progress to a file or not")
     flags.DEFINE_boolean("run", False, "Run the test suite for power")
     flags.DEFINE_string("power_monitor", None, "Type of power monitor to use")
+    flags.DEFINE_float("max_baseline_amps", 0.005,
+                       "Set maximum baseline current for device being tested")
     sys.exit(main(FLAGS(sys.argv)))
-
diff --git a/apps/CtsVerifier/res/drawable-hdpi/fs_clock.png b/apps/CtsVerifier/res/drawable-hdpi/fs_clock.png
new file mode 100644
index 0000000..209d78e
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable-hdpi/fs_clock.png
Binary files differ
diff --git a/apps/CtsVerifier/res/drawable-hdpi/ic_corp_icon.png b/apps/CtsVerifier/res/drawable-hdpi/ic_corp_icon.png
new file mode 100644
index 0000000..06c5135
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable-hdpi/ic_corp_icon.png
Binary files differ
diff --git a/apps/CtsVerifier/res/drawable-hdpi/ic_stat_alice.png b/apps/CtsVerifier/res/drawable-hdpi/ic_stat_alice.png
new file mode 100644
index 0000000..e4eea4b
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable-hdpi/ic_stat_alice.png
Binary files differ
diff --git a/apps/CtsVerifier/res/drawable-hdpi/ic_stat_bob.png b/apps/CtsVerifier/res/drawable-hdpi/ic_stat_bob.png
new file mode 100644
index 0000000..c67ff4f
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable-hdpi/ic_stat_bob.png
Binary files differ
diff --git a/apps/CtsVerifier/res/drawable-hdpi/ic_stat_charlie.png b/apps/CtsVerifier/res/drawable-hdpi/ic_stat_charlie.png
new file mode 100644
index 0000000..71afa3e
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable-hdpi/ic_stat_charlie.png
Binary files differ
diff --git a/apps/CtsVerifier/res/drawable-mdpi/fs_clock.png b/apps/CtsVerifier/res/drawable-mdpi/fs_clock.png
new file mode 100644
index 0000000..209d78e
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable-mdpi/fs_clock.png
Binary files differ
diff --git a/apps/CtsVerifier/res/drawable-mdpi/ic_corp_icon.png b/apps/CtsVerifier/res/drawable-mdpi/ic_corp_icon.png
new file mode 100644
index 0000000..79372b2
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable-mdpi/ic_corp_icon.png
Binary files differ
diff --git a/apps/CtsVerifier/res/drawable-mdpi/ic_stat_alice.png b/apps/CtsVerifier/res/drawable-mdpi/ic_stat_alice.png
new file mode 100644
index 0000000..3717827
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable-mdpi/ic_stat_alice.png
Binary files differ
diff --git a/apps/CtsVerifier/res/drawable-mdpi/ic_stat_bob.png b/apps/CtsVerifier/res/drawable-mdpi/ic_stat_bob.png
new file mode 100644
index 0000000..f266312
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable-mdpi/ic_stat_bob.png
Binary files differ
diff --git a/apps/CtsVerifier/res/drawable-mdpi/ic_stat_charlie.png b/apps/CtsVerifier/res/drawable-mdpi/ic_stat_charlie.png
new file mode 100644
index 0000000..49c4b9a
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable-mdpi/ic_stat_charlie.png
Binary files differ
diff --git a/apps/CtsVerifier/res/drawable-xhdpi/ic_corp_icon.png b/apps/CtsVerifier/res/drawable-xhdpi/ic_corp_icon.png
new file mode 100644
index 0000000..3626c7d
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable-xhdpi/ic_corp_icon.png
Binary files differ
diff --git a/apps/CtsVerifier/res/drawable-xxhdpi/ic_corp_icon.png b/apps/CtsVerifier/res/drawable-xxhdpi/ic_corp_icon.png
new file mode 100644
index 0000000..d33319f
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable-xxhdpi/ic_corp_icon.png
Binary files differ
diff --git a/apps/CtsVerifier/res/drawable-xxxhdpi/ic_corp_icon.png b/apps/CtsVerifier/res/drawable-xxxhdpi/ic_corp_icon.png
new file mode 100644
index 0000000..359e210
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable-xxxhdpi/ic_corp_icon.png
Binary files differ
diff --git a/apps/CtsVerifier/res/drawable/badged_icon.png b/apps/CtsVerifier/res/drawable/badged_icon.png
new file mode 100644
index 0000000..bb748da
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable/badged_icon.png
Binary files differ
diff --git a/apps/CtsVerifier/res/layout-land/sensor_test.xml b/apps/CtsVerifier/res/layout-land/sensor_test.xml
index 293b4b0..f547978 100644
--- a/apps/CtsVerifier/res/layout-land/sensor_test.xml
+++ b/apps/CtsVerifier/res/layout-land/sensor_test.xml
@@ -13,41 +13,46 @@
      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"
-        >
-
-    <LinearLayout
-            android:orientation="horizontal"
+<android.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <LinearLayout app:layout_box="all"
+            android:orientation="vertical"
             android:layout_width="match_parent"
-            android:layout_height="0dp"
-            android:layout_weight="1">
+            android:layout_height="match_parent"
+            >
 
-        <ScrollView
-                android:id="@+id/log_scroll_view"
-                android:fillViewport="true"
-                android:layout_height="match_parent"
-                android:layout_width="0dp"
+        <LinearLayout
+                android:orientation="horizontal"
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
                 android:layout_weight="1">
 
-            <LinearLayout
-                    android:id="@+id/log_layout"
-                    android:orientation="vertical"
-                    android:layout_width="match_parent"
-                    android:layout_height="match_parent"/>
+            <ScrollView
+                    android:id="@+id/log_scroll_view"
+                    android:fillViewport="true"
+                    android:layout_height="match_parent"
+                    android:layout_width="0dp"
+                    android:layout_weight="1">
 
-        </ScrollView>
+                <LinearLayout
+                        android:id="@+id/log_layout"
+                        android:orientation="vertical"
+                        android:layout_width="match_parent"
+                        android:layout_height="match_parent"/>
 
-        <android.opengl.GLSurfaceView android:id="@+id/gl_surface_view"
-                android:visibility="gone"
-                android:layout_width="0dp"
-                android:layout_height="match_parent"
-                android:layout_weight="1"/>
+            </ScrollView>
+
+            <android.opengl.GLSurfaceView android:id="@+id/gl_surface_view"
+                    android:visibility="gone"
+                    android:layout_width="0dp"
+                    android:layout_height="match_parent"
+                    android:layout_weight="1"/>
+
+        </LinearLayout>
+
+        <include layout="@layout/snsr_next_button" />
 
     </LinearLayout>
-
-    <include layout="@layout/snsr_next_button" />
-
-</LinearLayout>
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout-port/sensor_test.xml b/apps/CtsVerifier/res/layout-port/sensor_test.xml
index eac5357..b4eca4d 100644
--- a/apps/CtsVerifier/res/layout-port/sensor_test.xml
+++ b/apps/CtsVerifier/res/layout-port/sensor_test.xml
@@ -13,30 +13,35 @@
      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">
+<android.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <LinearLayout app:layout_box="all"
+            android:orientation="vertical"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
 
-    <ScrollView android:id="@+id/log_scroll_view"
-            android:fillViewport="true"
-            android:layout_height="0dp"
-            android:layout_weight="1"
-            android:layout_width="match_parent">
+        <ScrollView android:id="@+id/log_scroll_view"
+                android:fillViewport="true"
+                android:layout_height="0dp"
+                android:layout_weight="1"
+                android:layout_width="match_parent">
 
-        <LinearLayout android:id="@+id/log_layout"
-                android:orientation="vertical"
-                android:layout_height="match_parent"
+            <LinearLayout android:id="@+id/log_layout"
+                    android:orientation="vertical"
+                    android:layout_height="match_parent"
+                    android:layout_width="match_parent"/>
+
+        </ScrollView>
+
+        <android.opengl.GLSurfaceView android:id="@+id/gl_surface_view"
+                android:visibility="gone"
+                android:layout_height="0dp"
+                android:layout_weight="1"
                 android:layout_width="match_parent"/>
 
-    </ScrollView>
+        <include layout="@layout/snsr_next_button"/>
 
-    <android.opengl.GLSurfaceView android:id="@+id/gl_surface_view"
-            android:visibility="gone"
-            android:layout_height="0dp"
-            android:layout_weight="1"
-            android:layout_width="match_parent"/>
-
-    <include layout="@layout/snsr_next_button"/>
-
-</LinearLayout>
+    </LinearLayout>
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout/ble_client_connect.xml b/apps/CtsVerifier/res/layout/ble_client_connect.xml
deleted file mode 100644
index 54a0a99..0000000
--- a/apps/CtsVerifier/res/layout/ble_client_connect.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 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.
--->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="vertical"
-        android:padding="10dip"
-        >
-
-    <LinearLayout android:orientation="horizontal"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_centerInParent="true"
-            >
-        <EditText android:id="@+id/ble_address"
-                android:layout_weight="1"
-                android:layout_width="0dp"
-                android:layout_height="wrap_content"
-                android:hint="@string/ble_address"
-                />
-        <Button android:id="@+id/ble_connect"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/ble_connect"
-                />
-    </LinearLayout>
-
-    <include android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_alignParentBottom="true"
-            layout="@layout/pass_fail_buttons"
-            />
-</RelativeLayout>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/layout/ble_client_read_write.xml b/apps/CtsVerifier/res/layout/ble_client_read_write.xml
deleted file mode 100644
index 7edba62..0000000
--- a/apps/CtsVerifier/res/layout/ble_client_read_write.xml
+++ /dev/null
@@ -1,70 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 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.
--->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="vertical"
-        android:padding="10dip"
-        >
-    <LinearLayout android:orientation="vertical"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_centerInParent="true"
-            >
-        <LinearLayout android:orientation="horizontal"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                >
-            <EditText android:id="@+id/write_text"
-                    android:layout_width="0dp"
-                    android:layout_weight="1"
-                    android:layout_height="wrap_content"
-                    android:hint="@string/ble_write_hint"
-                    android:padding="10dip"
-                    />
-            <Button android:id="@+id/ble_write"
-                    android:layout_height="wrap_content"
-                    android:layout_width="wrap_content"
-                    android:text="@string/ble_write"
-                    />
-        </LinearLayout>
-
-        <LinearLayout android:orientation="horizontal"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                >
-            <TextView android:id="@+id/read_text"
-                    android:layout_width="0dp"
-                    android:layout_weight="1"
-                    android:layout_height="wrap_content"
-                    android:hint="@string/ble_read_hint"
-                    android:padding="10dip"
-                    android:textSize="18sp"
-                    />
-            <Button android:id="@+id/ble_read"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:text="@string/ble_read"
-                    />
-        </LinearLayout>
-    </LinearLayout>
-
-    <include android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_alignParentBottom="true"
-            layout="@layout/pass_fail_buttons"
-            />
-</RelativeLayout>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/layout/ble_client_test.xml b/apps/CtsVerifier/res/layout/ble_client_start.xml
similarity index 79%
rename from apps/CtsVerifier/res/layout/ble_client_test.xml
rename to apps/CtsVerifier/res/layout/ble_client_start.xml
index 660abd5..c377ca1 100644
--- a/apps/CtsVerifier/res/layout/ble_client_test.xml
+++ b/apps/CtsVerifier/res/layout/ble_client_start.xml
@@ -19,15 +19,17 @@
         android:orientation="vertical"
         android:padding="10dip"
         >
-    <ListView android:id="@+id/ble_client_tests"
-            android:layout_height="fill_parent"
+    <include android:id="@+id/pass_fail_buttons"
             android:layout_width="match_parent"
-            android:padding="10dip"
-            />
-
-    <include android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_alignParentBottom="true"
             layout="@layout/pass_fail_buttons"
             />
-</RelativeLayout>
\ No newline at end of file
+    <ListView android:id="@+id/ble_server_tests"
+            android:layout_height="wrap_content"
+            android:layout_width="match_parent"
+            android:layout_above="@id/pass_fail_buttons"
+            android:layout_alignParentTop="true"
+            android:padding="10dip"
+            />
+</RelativeLayout>
diff --git a/apps/CtsVerifier/res/layout/ble_notify_characteristic.xml b/apps/CtsVerifier/res/layout/ble_notify_characteristic.xml
deleted file mode 100644
index 786918a..0000000
--- a/apps/CtsVerifier/res/layout/ble_notify_characteristic.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 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.
--->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="vertical"
-        android:padding="10dip"
-        >
-    <RelativeLayout android:orientation="vertical"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_centerInParent="true"
-            >
-        <Button android:id="@+id/ble_notify"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_centerHorizontal="true"
-                android:text="@string/ble_begin_notification"
-                android:padding="10dip"
-                />
-        <TextView android:id="@+id/ble_notify_text"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_centerHorizontal="true"
-                android:layout_below="@id/ble_notify"
-                android:textSize="20sp"
-                android:padding="10dip"
-                />
-    </RelativeLayout>
-
-    <include android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_alignParentBottom="true"
-            layout="@layout/pass_fail_buttons"
-            />
-</RelativeLayout>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/layout/ble_read_rssi.xml b/apps/CtsVerifier/res/layout/ble_read_rssi.xml
deleted file mode 100644
index 8aa3193..0000000
--- a/apps/CtsVerifier/res/layout/ble_read_rssi.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 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.
--->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="vertical"
-        android:padding="10dip"
-        >
-    <RelativeLayout android:orientation="vertical"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_centerInParent="true"
-            >
-        <Button android:id="@+id/ble_read_rssi"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_centerHorizontal="true"
-                android:text="@string/ble_read_rssi"
-                android:padding="10dip"
-                />
-        <TextView android:id="@+id/ble_rssi_text"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_centerHorizontal="true"
-                android:layout_below="@id/ble_read_rssi"
-                android:textSize="20sp"
-                android:padding="10dip"
-                />
-    </RelativeLayout>
-
-    <include android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_alignParentBottom="true"
-            layout="@layout/pass_fail_buttons"
-            />
-</RelativeLayout>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/layout/ble_reliable_write.xml b/apps/CtsVerifier/res/layout/ble_reliable_write.xml
deleted file mode 100644
index 7db78ff..0000000
--- a/apps/CtsVerifier/res/layout/ble_reliable_write.xml
+++ /dev/null
@@ -1,63 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 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.
--->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="vertical"
-        android:padding="10dip"
-        >
-    <LinearLayout android:orientation="vertical"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_centerInParent="true"
-            >
-        <EditText android:id="@+id/write_text"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:hint="@string/ble_write_hint"
-                android:padding="5dip"
-                />
-        <LinearLayout android:orientation="horizontal"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                >
-            <Button android:id="@+id/ble_begin"
-                    android:layout_width="0dp"
-                    android:layout_height="wrap_content"
-                    android:layout_weight="1"
-                    android:text="@string/ble_begin_write"
-                    />
-            <Button android:id="@+id/ble_write"
-                    android:layout_width="0dp"
-                    android:layout_height="wrap_content"
-                    android:layout_weight="1"
-                    android:text="@string/ble_write"
-                    />
-            <Button android:id="@+id/ble_execute"
-                    android:layout_width="0dp"
-                    android:layout_height="wrap_content"
-                    android:layout_weight="1"
-                    android:text="@string/ble_execute_write"
-                    />
-        </LinearLayout>
-    </LinearLayout>
-
-    <include android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_alignParentBottom="true"
-            layout="@layout/pass_fail_buttons"
-            />
-</RelativeLayout>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/layout/ble_server_start.xml b/apps/CtsVerifier/res/layout/ble_server_start.xml
index 9ce714d..c377ca1 100644
--- a/apps/CtsVerifier/res/layout/ble_server_start.xml
+++ b/apps/CtsVerifier/res/layout/ble_server_start.xml
@@ -19,7 +19,7 @@
         android:orientation="vertical"
         android:padding="10dip"
         >
-    <include android:id="@+id/pass_fail_buttons" 
+    <include android:id="@+id/pass_fail_buttons"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_alignParentBottom="true"
@@ -32,4 +32,4 @@
             android:layout_alignParentTop="true"
             android:padding="10dip"
             />
-</RelativeLayout>
\ No newline at end of file
+</RelativeLayout>
diff --git a/apps/CtsVerifier/res/layout/ble_server_start_item.xml b/apps/CtsVerifier/res/layout/ble_test_item.xml
similarity index 100%
rename from apps/CtsVerifier/res/layout/ble_server_start_item.xml
rename to apps/CtsVerifier/res/layout/ble_test_item.xml
diff --git a/apps/CtsVerifier/res/layout/byod_custom_view.xml b/apps/CtsVerifier/res/layout/byod_custom_view.xml
new file mode 100644
index 0000000..00c9ad9
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/byod_custom_view.xml
@@ -0,0 +1,38 @@
+<?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:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+    <ScrollView android:id="@+id/scrollView"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingTop="2dip"
+            android:paddingBottom="12dip"
+            android:paddingStart="14dip"
+            android:paddingEnd="10dip"
+            android:overScrollMode="ifContentScrolls">
+        <TextView android:id="@+id/message"
+                style="@style/InstructionsSmallFont"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content" />
+    </ScrollView>
+
+    <ImageView android:id="@+id/sample_icon"
+            android:layout_width="56dip"
+            android:layout_height="56dip"
+            android:layout_gravity="center_horizontal" />
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/ca_boot_notify.xml b/apps/CtsVerifier/res/layout/ca_boot_notify.xml
index e9309d4..0ceece1 100644
--- a/apps/CtsVerifier/res/layout/ca_boot_notify.xml
+++ b/apps/CtsVerifier/res/layout/ca_boot_notify.xml
@@ -14,56 +14,60 @@
      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="fill_parent"
-  android:layout_height="fill_parent">
-
-  <ScrollView
+<android.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:layout_alignParentTop="true" >
+    android:layout_height="match_parent">
+    <LinearLayout app:layout_box="all"
+      android:orientation="vertical" android:layout_width="fill_parent"
+      android:layout_height="fill_parent">
 
-    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-      android:orientation="vertical"
-      android:layout_width="fill_parent"
-      android:layout_height="wrap_content">
+      <ScrollView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_alignParentTop="true" >
 
-      <TextView
-          android:id="@+id/check_cert_desc"
-          android:layout_width="wrap_content"
-          android:layout_height="wrap_content"
-          android:text="@string/caboot_check_cert_installed"/>
+        <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+          android:orientation="vertical"
+          android:layout_width="fill_parent"
+          android:layout_height="wrap_content">
 
-      <Button android:id="@+id/check_creds"
-          android:layout_width="wrap_content"
-          android:layout_height="wrap_content"
-          android:text="@string/caboot_check_creds" />
+          <TextView
+              android:id="@+id/check_cert_desc"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:text="@string/caboot_check_cert_installed"/>
 
-      <TextView
-          android:id="@+id/need_to_install_cert"
-          android:layout_width="wrap_content"
-          android:layout_height="wrap_content"
-          android:text="@string/caboot_if_not_installed"/>
+          <Button android:id="@+id/check_creds"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:text="@string/caboot_check_creds" />
 
-      <Button android:id="@+id/install"
-          android:layout_width="wrap_content"
-          android:layout_height="wrap_content"
-          android:text="@string/caboot_install_cert" />
+          <TextView
+              android:id="@+id/need_to_install_cert"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:text="@string/caboot_if_not_installed"/>
 
-      <TextView
-          android:id="@+id/reboot"
-          android:layout_width="wrap_content"
-          android:layout_height="wrap_content"
-          android:text="@string/caboot_reboot_desc"/>
+          <Button android:id="@+id/install"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:text="@string/caboot_install_cert" />
 
-      <TextView
-          android:id="@+id/after_reboot"
-          android:layout_width="wrap_content"
-          android:layout_height="wrap_content"
-          android:text="@string/caboot_after_boot"/>
+          <TextView
+              android:id="@+id/reboot"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:text="@string/caboot_reboot_desc"/>
 
-      <include layout="@layout/pass_fail_buttons" />
+          <TextView
+              android:id="@+id/after_reboot"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:text="@string/caboot_after_boot"/>
+
+          <include layout="@layout/pass_fail_buttons" />
+        </LinearLayout>
+      </ScrollView>
     </LinearLayout>
-  </ScrollView>
-</LinearLayout>
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout/ca_main.xml b/apps/CtsVerifier/res/layout/ca_main.xml
index 467ed01..274430d 100644
--- a/apps/CtsVerifier/res/layout/ca_main.xml
+++ b/apps/CtsVerifier/res/layout/ca_main.xml
@@ -14,65 +14,68 @@
      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="fill_parent"
-  android:layout_height="fill_parent">
+<android.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <LinearLayout app:layout_box="all"
+      android:orientation="vertical" android:layout_width="fill_parent"
+      android:layout_height="fill_parent">
 
 
-  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="horizontal" android:layout_width="fill_parent"
-    android:layout_height="wrap_content">
-    <!--Button android:id="@+id/focusmodesbutton" android:layout_width="0px"
-      android:layout_height="wrap_content" android:text="@string/ca_focus_modes_label"
-      android:layout_weight="1" /-->
-    <Button android:id="@+id/findcheckerboardbutton" android:layout_width="0px"
-      android:layout_height="wrap_content" android:text="@string/ca_find_checkerboard_label"
-      android:layout_weight="1" />
+      <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:orientation="horizontal" android:layout_width="fill_parent"
+        android:layout_height="wrap_content">
+        <!--Button android:id="@+id/focusmodesbutton" android:layout_width="0px"
+          android:layout_height="wrap_content" android:text="@string/ca_focus_modes_label"
+          android:layout_weight="1" /-->
+        <Button android:id="@+id/findcheckerboardbutton" android:layout_width="0px"
+          android:layout_height="wrap_content" android:text="@string/ca_find_checkerboard_label"
+          android:layout_weight="1" />
 
-    <Button android:id="@+id/meteringbutton" android:layout_width="0px"
-      android:layout_height="wrap_content" android:text="@string/ca_metering_label"
-      android:layout_weight="1" />
+        <Button android:id="@+id/meteringbutton" android:layout_width="0px"
+          android:layout_height="wrap_content" android:text="@string/ca_metering_label"
+          android:layout_weight="1" />
 
-    <Button android:id="@+id/exposurecompensationbutton" android:layout_width="0px"
-      android:layout_height="wrap_content" android:text="@string/ca_exposure_test_label"
-      android:layout_weight="1"/>
+        <Button android:id="@+id/exposurecompensationbutton" android:layout_width="0px"
+          android:layout_height="wrap_content" android:text="@string/ca_exposure_test_label"
+          android:layout_weight="1"/>
 
-    <Button android:id="@+id/whitebalancebutton" android:layout_width="0px"
-      android:layout_height="wrap_content" android:text="@string/ca_wb_test_label"
-      android:layout_weight="1" />
+        <Button android:id="@+id/whitebalancebutton" android:layout_width="0px"
+          android:layout_height="wrap_content" android:text="@string/ca_wb_test_label"
+          android:layout_weight="1" />
 
-    <Button android:id="@+id/lockbutton" android:layout_width="0px"
-      android:layout_height="wrap_content" android:text="@string/ca_lock_test_label"
-      android:layout_weight="1" />
-  </LinearLayout>
+        <Button android:id="@+id/lockbutton" android:layout_width="0px"
+          android:layout_height="wrap_content" android:text="@string/ca_lock_test_label"
+          android:layout_weight="1" />
+      </LinearLayout>
 
-  <LinearLayout android:orientation="horizontal"
-    android:layout_width="fill_parent" android:layout_height="0px"
-    android:layout_weight="1">
+      <LinearLayout android:orientation="horizontal"
+        android:layout_width="fill_parent" android:layout_height="0px"
+        android:layout_weight="1">
 
-    <SurfaceView android:id="@+id/cameraview" android:layout_height="fill_parent"
-      android:layout_width="wrap_content"
-      android:layout_weight="0" />
+        <SurfaceView android:id="@+id/cameraview" android:layout_height="fill_parent"
+          android:layout_width="wrap_content"
+          android:layout_weight="0" />
 
-    <LinearLayout android:orientation="vertical"
-      android:layout_width="fill_parent" android:layout_height="match_parent"
-      android:layout_weight="1">
+        <LinearLayout android:orientation="vertical"
+          android:layout_width="fill_parent" android:layout_height="match_parent"
+          android:layout_weight="1">
 
-       <ListView android:id="@+id/ca_tests"
+           <ListView android:id="@+id/ca_tests"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:layout_marginLeft="10px"/>
+
+          <ImageView android:id="@+id/resultview" android:layout_height="wrap_content"
             android:layout_width="fill_parent"
-            android:layout_height="wrap_content"
-            android:layout_weight="1"
-            android:layout_marginLeft="10px"/>
+            android:layout_weight="1" />
+        </LinearLayout>
 
-      <ImageView android:id="@+id/resultview" android:layout_height="wrap_content"
-        android:layout_width="fill_parent"
-        android:layout_weight="1" />
+      </LinearLayout>
+
+      <include layout="@layout/pass_fail_buttons" />
+
     </LinearLayout>
-
-  </LinearLayout>
-
-  <include layout="@layout/pass_fail_buttons" />
-
-</LinearLayout>
-
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout/cainstallnotify_main.xml b/apps/CtsVerifier/res/layout/cainstallnotify_main.xml
index 16882bd..6cb6160 100644
--- a/apps/CtsVerifier/res/layout/cainstallnotify_main.xml
+++ b/apps/CtsVerifier/res/layout/cainstallnotify_main.xml
@@ -14,32 +14,37 @@
      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.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical"
-    android:padding="10dip" >
-
-    <ScrollView
-        android:id="@+id/ca_notify_test_scroller"
+    android:layout_height="match_parent">
+    <LinearLayout xmlns:app="http://schemas.android.com/apk/res-auto"
         android:layout_width="match_parent"
-        android:layout_height="0dp"
-        android:layout_weight="1"
+        android:layout_height="match_parent"
         android:orientation="vertical"
         android:padding="10dip" >
 
-        <LinearLayout
-            android:id="@+id/ca_notify_test_items"
+        <ScrollView
+            android:id="@+id/ca_notify_test_scroller"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:layout_weight="1"
+            android:orientation="vertical"
+            android:padding="10dip" >
+
+            <LinearLayout
+                android:id="@+id/ca_notify_test_items"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical" >
+            </LinearLayout>
+        </ScrollView>
+
+        <include
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:orientation="vertical" >
-        </LinearLayout>
-    </ScrollView>
+            android:layout_weight="0"
+            layout="@layout/pass_fail_buttons" />
 
-    <include
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_weight="0"
-        layout="@layout/pass_fail_buttons" />
-
-</LinearLayout>
\ No newline at end of file
+    </LinearLayout>
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout/fs_main.xml b/apps/CtsVerifier/res/layout/fs_main.xml
index 7473f0f..8a78c81 100644
--- a/apps/CtsVerifier/res/layout/fs_main.xml
+++ b/apps/CtsVerifier/res/layout/fs_main.xml
@@ -13,29 +13,34 @@
      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">
+<android.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <LinearLayout app:layout_box="all"
+             android:orientation="vertical"
+             android:layout_width="match_parent"
+             android:layout_height="match_parent">
 
-     <TextView android:id="@+id/fs_warnings"
-               android:layout_width="wrap_content"
-               android:layout_height="wrap_content"
-               android:text="@string/empty"/>
+         <TextView android:id="@+id/fs_warnings"
+                   android:layout_width="wrap_content"
+                   android:layout_height="wrap_content"
+                   android:text="@string/empty"/>
 
-     <ListView android:id="@id/android:list"
-               android:layout_width="match_parent"
-               android:layout_height="match_parent"
-               android:background="#000000"
-               android:layout_weight="1"
-               android:drawSelectorOnTop="false"/>
+         <ListView android:id="@id/android:list"
+                   android:layout_width="match_parent"
+                   android:layout_height="match_parent"
+                   android:background="#000000"
+                   android:layout_weight="1"
+                   android:drawSelectorOnTop="false"/>
 
-     <TextView android:id="@id/android:empty"
-               android:layout_width="match_parent"
-               android:layout_height="match_parent"
-               android:background="#000000"
-               android:text="@string/fs_no_data"/>
+         <TextView android:id="@id/android:empty"
+                   android:layout_width="match_parent"
+                   android:layout_height="match_parent"
+                   android:background="#000000"
+                   android:text="@string/fs_no_data"/>
 
-    <include layout="@layout/pass_fail_buttons" />
+        <include layout="@layout/pass_fail_buttons" />
 
-</LinearLayout>
+    </LinearLayout>
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout/intent_driven_test.xml b/apps/CtsVerifier/res/layout/intent_driven_test.xml
index 00c1cf6..bd9e4ca 100644
--- a/apps/CtsVerifier/res/layout/intent_driven_test.xml
+++ b/apps/CtsVerifier/res/layout/intent_driven_test.xml
@@ -1,30 +1,36 @@
 <?xml version="1.0" encoding="utf-8"?>
 
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<android.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical">
-
-  <ScrollView
-      android:layout_width="match_parent"
-      android:layout_height="0dp"
-      android:layout_weight="1">
-    <TextView android:id="@+id/info"
+    android:layout_height="match_parent">
+    <LinearLayout app:layout_box="all"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:textSize="18sp"
-        android:padding="5dp"
-        android:text="@string/dc_start_alarm_test_info"/>
-  </ScrollView>
+        android:layout_height="match_parent"
+        android:orientation="vertical">
 
-  <LinearLayout android:id="@+id/buttons"
-      android:orientation="horizontal"
-      android:layout_width="wrap_content"
-      android:layout_height="wrap_content"/>
+        <ScrollView
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:layout_weight="1">
+            <TextView android:id="@+id/info"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:textSize="18sp"
+                android:padding="5dp"
+                android:text="@string/dc_start_alarm_test_info"/>
+        </ScrollView>
 
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content">
-      <include layout="@layout/pass_fail_buttons"/>
-  </LinearLayout>
-</LinearLayout>
+        <LinearLayout android:id="@+id/buttons"
+            android:orientation="horizontal"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"/>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <include layout="@layout/pass_fail_buttons"/>
+        </LinearLayout>
+    </LinearLayout>
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout/js_charging.xml b/apps/CtsVerifier/res/layout/js_charging.xml
index 4c0e552..8d9ed1d 100644
--- a/apps/CtsVerifier/res/layout/js_charging.xml
+++ b/apps/CtsVerifier/res/layout/js_charging.xml
@@ -1,67 +1,76 @@
 <?xml version="1.0" encoding="utf-8"?>
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="vertical" android:layout_width="match_parent"
+<android.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
     android:layout_height="match_parent">
-    <TextView
+    <ScrollView app:layout_box="all"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:text="@string/js_test_description"
-        android:layout_margin="@dimen/js_padding"/>
-    <TextView
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_margin="@dimen/js_padding"
-        android:text="@string/js_charging_description_1"
-        android:textStyle="bold"/>
-    <Button
-        android:id="@+id/js_charging_start_test_button"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center"
-        android:text="@string/js_start_test_text"
-        android:onClick="startTest"
-        android:enabled="false"/>
+        android:layout_height="match_parent">
+        <LinearLayout
+            android:orientation="vertical"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/js_test_description"
+                android:layout_margin="@dimen/js_padding"/>
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_margin="@dimen/js_padding"
+                android:text="@string/js_charging_description_1"
+                android:textStyle="bold"/>
+            <Button
+                android:id="@+id/js_charging_start_test_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:text="@string/js_start_test_text"
+                android:onClick="startTest"
+                android:enabled="false"/>
 
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="@dimen/js_padding"
-        android:layout_marginBottom="@dimen/js_padding">
-        <ImageView
-            android:id="@+id/charging_off_test_image"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:src="@drawable/fs_indeterminate"
-            android:layout_marginRight="@dimen/js_padding"/>
-        <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/js_charging_off_test"
-            android:textSize="16dp"/>
-    </LinearLayout>
-    <TextView
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_margin="@dimen/js_padding"
-        android:text="@string/js_charging_description_2"
-        android:textStyle="bold"/>
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="@dimen/js_padding"
-        android:layout_marginBottom="@dimen/js_padding">
-        <ImageView
-            android:id="@+id/charging_on_test_image"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:src="@drawable/fs_indeterminate"
-            android:layout_marginRight="@dimen/js_padding"/>
-        <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/js_charging_on_test"
-            android:textSize="16dp"/>
-    </LinearLayout>
-    <include layout="@layout/pass_fail_buttons" />
-</LinearLayout>
\ No newline at end of file
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/js_padding"
+                android:layout_marginBottom="@dimen/js_padding">
+                <ImageView
+                    android:id="@+id/charging_off_test_image"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:src="@drawable/fs_indeterminate"
+                    android:layout_marginRight="@dimen/js_padding"/>
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/js_charging_off_test"
+                    android:textSize="16dp"/>
+            </LinearLayout>
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_margin="@dimen/js_padding"
+                android:text="@string/js_charging_description_2"
+                android:textStyle="bold"/>
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/js_padding"
+                android:layout_marginBottom="@dimen/js_padding">
+                <ImageView
+                    android:id="@+id/charging_on_test_image"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:src="@drawable/fs_indeterminate"
+                    android:layout_marginRight="@dimen/js_padding"/>
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/js_charging_on_test"
+                    android:textSize="16dp"/>
+            </LinearLayout>
+            <include layout="@layout/pass_fail_buttons" />
+        </LinearLayout>
+    </ScrollView>
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout/js_connectivity.xml b/apps/CtsVerifier/res/layout/js_connectivity.xml
index 5208c18..b0e2824 100644
--- a/apps/CtsVerifier/res/layout/js_connectivity.xml
+++ b/apps/CtsVerifier/res/layout/js_connectivity.xml
@@ -1,83 +1,91 @@
 <?xml version="1.0" encoding="utf-8"?>
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="vertical" android:layout_width="match_parent"
+<android.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
     android:layout_height="match_parent">
-    <TextView
+    <ScrollView app:layout_box="all"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:text="@string/js_test_description"
-        android:layout_margin="@dimen/js_padding"/>
+        android:layout_height="match_parent">
+        <LinearLayout
+            android:orientation="vertical" android:layout_width="match_parent"
+            android:layout_height="match_parent">
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/js_test_description"
+                android:layout_margin="@dimen/js_padding"/>
 
-    <TextView
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:text="@string/js_connectivity_description_1"
-        android:layout_margin="@dimen/js_padding"
-        android:textStyle="bold"/>
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/js_connectivity_description_1"
+                android:layout_margin="@dimen/js_padding"
+                android:textStyle="bold"/>
 
-    <Button
-        android:id="@+id/js_connectivity_start_test_button"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center"
-        android:text="@string/js_start_test_text"
-        android:onClick="startTest"
-        android:enabled="false"/>
+            <Button
+                android:id="@+id/js_connectivity_start_test_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:text="@string/js_start_test_text"
+                android:onClick="startTest"
+                android:enabled="false"/>
 
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="@dimen/js_padding"
-        android:layout_marginBottom="@dimen/js_padding">
-        <ImageView
-            android:id="@+id/connectivity_off_test_unmetered_image"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:src="@drawable/fs_indeterminate"
-            android:layout_marginRight="@dimen/js_padding"/>
-        <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/js_unmetered_connectivity_test"
-            android:textSize="16dp"/>
-    </LinearLayout>
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/js_padding"
+                android:layout_marginBottom="@dimen/js_padding">
+                <ImageView
+                    android:id="@+id/connectivity_off_test_unmetered_image"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:src="@drawable/fs_indeterminate"
+                    android:layout_marginRight="@dimen/js_padding"/>
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/js_unmetered_connectivity_test"
+                    android:textSize="16dp"/>
+            </LinearLayout>
 
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="@dimen/js_padding"
-        android:layout_marginBottom="@dimen/js_padding">
-        <ImageView
-            android:id="@+id/connectivity_off_test_any_connectivity_image"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:src="@drawable/fs_indeterminate"
-            android:layout_marginRight="@dimen/js_padding"/>
-        <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/js_any_connectivity_test"
-            android:textSize="16dp"/>
-    </LinearLayout>
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/js_padding"
+                android:layout_marginBottom="@dimen/js_padding">
+                <ImageView
+                    android:id="@+id/connectivity_off_test_any_connectivity_image"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:src="@drawable/fs_indeterminate"
+                    android:layout_marginRight="@dimen/js_padding"/>
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/js_any_connectivity_test"
+                    android:textSize="16dp"/>
+            </LinearLayout>
 
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="@dimen/js_padding"
-        android:layout_marginBottom="@dimen/js_padding">
-        <ImageView
-            android:id="@+id/connectivity_off_test_no_connectivity_image"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:src="@drawable/fs_indeterminate"
-            android:layout_marginRight="@dimen/js_padding"/>
-        <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/js_no_connectivity_test"
-            android:textSize="16dp"/>
-    </LinearLayout>
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/js_padding"
+                android:layout_marginBottom="@dimen/js_padding">
+                <ImageView
+                    android:id="@+id/connectivity_off_test_no_connectivity_image"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:src="@drawable/fs_indeterminate"
+                    android:layout_marginRight="@dimen/js_padding"/>
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/js_no_connectivity_test"
+                    android:textSize="16dp"/>
+            </LinearLayout>
 
-    <include layout="@layout/pass_fail_buttons" />
-</LinearLayout>
\ No newline at end of file
+            <include layout="@layout/pass_fail_buttons" />
+        </LinearLayout>
+    </ScrollView>
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout/js_idle.xml b/apps/CtsVerifier/res/layout/js_idle.xml
index 90e55ec..4277173 100644
--- a/apps/CtsVerifier/res/layout/js_idle.xml
+++ b/apps/CtsVerifier/res/layout/js_idle.xml
@@ -1,63 +1,71 @@
 <?xml version="1.0" encoding="utf-8"?>
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="vertical" android:layout_width="match_parent"
+<android.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
     android:layout_height="match_parent">
-
-    <TextView
+    <ScrollView app:layout_box="all"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:text="@string/js_test_description"
-        android:layout_margin="@dimen/js_padding"/>
-    <TextView
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:text="@string/js_idle_description_1"
-        android:layout_margin="@dimen/js_padding"
-        android:textStyle="bold"/>
+        android:layout_height="match_parent">
+        <LinearLayout
+            android:orientation="vertical"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/js_test_description"
+                android:layout_margin="@dimen/js_padding"/>
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/js_idle_description_1"
+                android:layout_margin="@dimen/js_padding"
+                android:textStyle="bold"/>
 
-    <Button
-        android:id="@+id/js_idle_start_test_button"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center"
-        android:text="@string/js_start_test_text"
-        android:onClick="startTest"
-        android:enabled="false"/>
+            <Button
+                android:id="@+id/js_idle_start_test_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:text="@string/js_start_test_text"
+                android:onClick="startTest"
+                android:enabled="false"/>
 
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="@dimen/js_padding"
-        android:layout_marginBottom="@dimen/js_padding">
-        <ImageView
-            android:id="@+id/idle_off_test_image"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:src="@drawable/fs_indeterminate"
-            android:layout_marginRight="@dimen/js_padding"/>
-        <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/js_idle_item_idle_off"
-            android:textSize="16dp"/>
-    </LinearLayout>
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="@dimen/js_padding"
-        android:layout_marginBottom="@dimen/js_padding">
-        <ImageView
-            android:id="@+id/idle_on_test_image"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:src="@drawable/fs_indeterminate"
-            android:layout_marginRight="@dimen/js_padding"/>
-        <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/js_idle_item_idle_on"
-            android:textSize="16dp"/>
-    </LinearLayout>
-    <include layout="@layout/pass_fail_buttons" />
-</LinearLayout>
\ No newline at end of file
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/js_padding"
+                android:layout_marginBottom="@dimen/js_padding">
+                <ImageView
+                    android:id="@+id/idle_off_test_image"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:src="@drawable/fs_indeterminate"
+                    android:layout_marginRight="@dimen/js_padding"/>
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/js_idle_item_idle_off"
+                    android:textSize="16dp"/>
+            </LinearLayout>
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/js_padding"
+                android:layout_marginBottom="@dimen/js_padding">
+                <ImageView
+                    android:id="@+id/idle_on_test_image"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:src="@drawable/fs_indeterminate"
+                    android:layout_marginRight="@dimen/js_padding"/>
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/js_idle_item_idle_on"
+                    android:textSize="16dp"/>
+            </LinearLayout>
+            <include layout="@layout/pass_fail_buttons" />
+        </LinearLayout>
+    </ScrollView>
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout/location_mode_main.xml b/apps/CtsVerifier/res/layout/location_mode_main.xml
index fde6aba..1768434 100644
--- a/apps/CtsVerifier/res/layout/location_mode_main.xml
+++ b/apps/CtsVerifier/res/layout/location_mode_main.xml
@@ -14,32 +14,37 @@
      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.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical"
-    android:padding="10dip" >
-
-    <ScrollView
-        android:id="@+id/test_scroller"
+    android:layout_height="match_parent">
+    <LinearLayout app:layout_box="all"
         android:layout_width="match_parent"
-        android:layout_height="0dp"
-        android:layout_weight="1"
+        android:layout_height="match_parent"
         android:orientation="vertical"
         android:padding="10dip" >
 
-        <LinearLayout
-            android:id="@+id/test_items"
+        <ScrollView
+            android:id="@+id/test_scroller"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:layout_weight="1"
+            android:orientation="vertical"
+            android:padding="10dip" >
+
+            <LinearLayout
+                android:id="@+id/test_items"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical" >
+            </LinearLayout>
+        </ScrollView>
+
+        <include
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:orientation="vertical" >
-        </LinearLayout>
-    </ScrollView>
+            android:layout_weight="0"
+            layout="@layout/pass_fail_buttons" />
 
-    <include
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_weight="0"
-        layout="@layout/pass_fail_buttons" />
-
-</LinearLayout>
+    </LinearLayout>
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout/pa_main.xml b/apps/CtsVerifier/res/layout/pa_main.xml
index 76cb7d4..832af71 100644
--- a/apps/CtsVerifier/res/layout/pa_main.xml
+++ b/apps/CtsVerifier/res/layout/pa_main.xml
@@ -13,19 +13,24 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<android.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
-    android:layout_height="match_parent" >
-
-    <include
-        android:id="@+id/pass_fail_buttons"
-        android:layout_gravity="top"
-        layout="@layout/pass_fail_buttons" />
-
-    <TextureView
-        android:id="@+id/texture_view"
+    android:layout_height="match_parent">
+    <RelativeLayout app:layout_box="all"
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_below="@id/pass_fail_buttons" />
+        android:layout_height="match_parent" >
 
-</RelativeLayout>
+        <include
+            android:id="@+id/pass_fail_buttons"
+            android:layout_gravity="top"
+            layout="@layout/pass_fail_buttons" />
+
+        <TextureView
+            android:id="@+id/texture_view"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_below="@id/pass_fail_buttons" />
+
+    </RelativeLayout>
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout/pass_fail_list.xml b/apps/CtsVerifier/res/layout/pass_fail_list.xml
index 0b247f4..cdd40e1 100644
--- a/apps/CtsVerifier/res/layout/pass_fail_list.xml
+++ b/apps/CtsVerifier/res/layout/pass_fail_list.xml
@@ -13,24 +13,30 @@
      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"
-        >
+<android.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
 
-    <ListView android:id="@id/android:list"
+    <LinearLayout app:layout_box="all"
+            android:orientation="vertical"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
-            android:layout_weight="1"
-            />
+            >
 
-    <TextView android:id="@id/android:empty"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:layout_weight="1"
-            />
+        <ListView android:id="@id/android:list"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                />
 
-    <include layout="@layout/pass_fail_buttons" />
+        <TextView android:id="@id/android:empty"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                />
 
-</LinearLayout>
+        <include layout="@layout/pass_fail_buttons" />
+
+    </LinearLayout>
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout/poa_main.xml b/apps/CtsVerifier/res/layout/poa_main.xml
index 578a6a6..41bade0 100644
--- a/apps/CtsVerifier/res/layout/poa_main.xml
+++ b/apps/CtsVerifier/res/layout/poa_main.xml
@@ -13,17 +13,22 @@
      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="fill_parent"
-    android:layout_height="fill_parent"
-    android:orientation="vertical" >
+<android.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <LinearLayout app:layout_box="all"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:orientation="vertical" >
 
-    <include layout="@layout/pass_fail_buttons" />
+        <include layout="@layout/pass_fail_buttons" />
 
-    <TextView
-        android:id="@+id/poa_status_text"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:textAppearance="@style/InstructionsFont" />
+        <TextView
+            android:id="@+id/poa_status_text"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:textAppearance="@style/InstructionsFont" />
 
-</LinearLayout>
\ No newline at end of file
+    </LinearLayout>
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout/provisioning_byod.xml b/apps/CtsVerifier/res/layout/provisioning_byod.xml
index 989266f..b1b75ba 100644
--- a/apps/CtsVerifier/res/layout/provisioning_byod.xml
+++ b/apps/CtsVerifier/res/layout/provisioning_byod.xml
@@ -21,8 +21,8 @@
 
     <ScrollView
             android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_weight="1">
+            android:layout_height="320dp"
+            android:layout_weight="2">
         <TextView
                 android:id="@+id/byod_instructions"
                 android:layout_width="match_parent"
@@ -39,9 +39,10 @@
         android:text="@string/provisioning_byod_start" />
 
     <ListView
-        android:id="@id/android:list"
+        android:id="@+id/android:list"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content" />
+        android:layout_height="wrap_content"
+        android:layout_weight="3" />
 
     <include layout="@layout/pass_fail_buttons" />
 
diff --git a/apps/CtsVerifier/res/layout/pwa_widgets.xml b/apps/CtsVerifier/res/layout/pwa_widgets.xml
index 4bfcec6..537fc32 100644
--- a/apps/CtsVerifier/res/layout/pwa_widgets.xml
+++ b/apps/CtsVerifier/res/layout/pwa_widgets.xml
@@ -13,16 +13,19 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<android.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
-    android:layout_height="match_parent" >
+    android:layout_height="match_parent">
 
      <TextureView
+         app:layout_box="all"
          android:id="@+id/texture_view"
          android:layout_width="match_parent"
          android:layout_height="match_parent" />
 
      <LinearLayout
+         app:layout_box="all"
          android:layout_width="fill_parent"
          android:layout_height="wrap_content"
          android:orientation="vertical" >
@@ -70,4 +73,4 @@
          </LinearLayout>
      </LinearLayout>
 
-</FrameLayout>
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout/screen_pinning.xml b/apps/CtsVerifier/res/layout/screen_pinning.xml
new file mode 100644
index 0000000..f1d7b65
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/screen_pinning.xml
@@ -0,0 +1,62 @@
+<?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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <TextView
+        android:id="@+id/error_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+    <ScrollView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true" >
+
+        <RelativeLayout
+            android:id="@+id/instructions_group"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical" >
+
+            <LinearLayout
+                android:id="@+id/instructions_list"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:orientation="vertical" >
+
+            </LinearLayout>
+
+            <Button
+                android:id="@+id/next_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentEnd="true"
+                android:layout_below="@id/instructions_list"
+                android:text="@string/next_button_text" />
+
+        </RelativeLayout>
+    </ScrollView>
+
+    <include
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        layout="@layout/pass_fail_buttons" />
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/layout/test_list_footer.xml b/apps/CtsVerifier/res/layout/test_list_footer.xml
new file mode 100644
index 0000000..fdb8e43
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/test_list_footer.xml
@@ -0,0 +1,38 @@
+<?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
+  -->
+<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <Button
+        android:id="@+id/clear"
+        android:text="@string/clear"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+    <Button
+        android:id="@+id/view"
+        android:text="@string/view"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+    <Button
+        android:id="@+id/export"
+        android:text="@string/export"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+</GridLayout>
diff --git a/apps/CtsVerifier/res/layout/tv_item.xml b/apps/CtsVerifier/res/layout/tv_item.xml
new file mode 100644
index 0000000..e7311f9
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/tv_item.xml
@@ -0,0 +1,53 @@
+<?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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content" >
+
+    <ImageView
+        android:id="@+id/status"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentTop="true"
+        android:layout_marginTop="10dip"
+        android:contentDescription="@string/pass_button_text"
+        android:padding="10dip"
+        android:src="@drawable/fs_indeterminate" />
+
+    <TextView
+        android:id="@+id/instructions"
+        style="@style/InstructionsSmallFont"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentRight="true"
+        android:layout_alignParentTop="true"
+        android:layout_toRightOf="@id/status" />
+
+    <Button
+        android:id="@+id/user_action_button"
+        android:enabled="false"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentRight="true"
+        android:layout_below="@id/instructions"
+        android:layout_marginLeft="20dip"
+        android:layout_marginRight="20dip"
+        android:layout_toRightOf="@id/status"
+        android:visibility="gone" />
+
+</RelativeLayout>
diff --git a/apps/CtsVerifier/res/layout/tv_overlay.xml b/apps/CtsVerifier/res/layout/tv_overlay.xml
new file mode 100644
index 0000000..8cd5dd8
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/tv_overlay.xml
@@ -0,0 +1,29 @@
+<?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">
+
+    <TextView
+            android:id="@+id/overlay_view_text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="20sp"
+            android:text="@string/overlay_view_text">
+    </TextView>
+
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index ec3f8d0..bd748ac 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -36,6 +36,7 @@
     <string name="test_category_features">Features</string>
     <string name="test_category_deskclock">Clock</string>
     <string name="test_category_jobscheduler">Job Scheduler</string>
+    <string name="test_category_tv">TV</string>
     <string name="test_category_other">Other</string>
     <string name="clear">Clear</string>
     <string name="test_results_cleared">Test results cleared.</string>
@@ -187,14 +188,16 @@
     <!-- BLE client side strings -->
     <string name="ble_client_service_name">Bluetooth LE GATT Client Handler Service</string>
     <string name="ble_client_test_name">BLE Client Test</string>
-    <string name="ble_client_connect_name">1. BLE Client Connect</string>
-    <string name="ble_discover_service_name">2. BLE Discover Service</string>
-    <string name="ble_client_characteristic_name">3. BLE Read/Write Characteristic</string>
-    <string name="ble_reliable_write_name">4. BLE Reliable Write</string>
-    <string name="ble_notify_characteristic_name">5. BLE Notify Characteristic</string>
-    <string name="ble_client_descriptor_name">6. BLE Read/Write Descriptor</string>
-    <string name="ble_read_rssi_name">7. BLE Read RSSI</string>
-    <string name="ble_client_disconnect_name">8. BLE Client Disconnect</string>
+    <string name="ble_client_connect_name">BLE Client Connect</string>
+    <string name="ble_discover_service_name">BLE Discover Service</string>
+    <string name="ble_read_characteristic_name">BLE Read Characteristic</string>
+    <string name="ble_write_characteristic_name">BLE Write Characteristic</string>
+    <string name="ble_reliable_write_name">BLE Reliable Write</string>
+    <string name="ble_notify_characteristic_name">BLE Notify Characteristic</string>
+    <string name="ble_read_descriptor_name">BLE Read Descriptor</string>
+    <string name="ble_write_descriptor_name">BLE Write Descriptor</string>
+    <string name="ble_read_rssi_name">BLE Read RSSI</string>
+    <string name="ble_client_disconnect_name">BLE Client Disconnect</string>
     <string name="ble_client_test_info">The BLE test must be done simultaneously on two devices. This device is the client. All tests listed here must be done in order.</string>
     <string name="ble_client_send_connect_info">Type in the Bluetooth address of the remote device to connect to, and verify that the devices are connected.</string>
     <string name="ble_discover_service_info">Verify that the service is discovered when you press the "Discover Service" button.</string>
@@ -217,6 +220,7 @@
     <string name="ble_waiting_notification">Waiting on notification</string>
     <string name="ble_read_rssi">Read RSSI</string>
     <string name="ble_disconnect">Disconnect</string>
+    <string name="ble_test_text">TEST</string>
 
     <!-- BLE server side strings -->
     <string name="ble_server_service_name">Bluetooth LE GATT Server Handler Service</string>
@@ -266,6 +270,8 @@
     <string name="ble_scanner_scan_filter_instruction">Scan filter is to scan data with service UUID = 0x6666 only. If you scan without scan filter, data with service UUID = 0x5555 and 0x6666 will show up on screen.\nFor monsoon test:\n\tClick scan with filter, lock the screen, connect to monsoon. It will not wake up when advertiser is advertising unscannable data packets, but will show a peak in power usage when advertiser is advertising scannable data.\nFor logcat test:\n\tClick scan with filter, logcat the scanner. No data will be received by GattService when advertiser is advertising unscannable data.</string>
     <string name="ble_scan_with_filter">Scan with filter</string>
     <string name="ble_scan_without_filter">Scan without filter</string>
+    <string name="ble_scan_start">Start scan</string>
+    <string name="ble_scan_stop">Stop scan</string>
 
     <!-- Strings for FeatureSummaryActivity -->
     <string name="feature_summary">Hardware/Software Feature Summary</string>
@@ -437,6 +443,10 @@
     <string name="nfc_hce_payment_dynamic_aids_reader">Dynamic payment AIDs (Reader)</string>
     <string name="nfc_hce_payment_dynamic_aids_help">This test tries to register dynamic AIDs for a payment service.</string>
 
+    <string name="nfc_hce_large_num_aids_emulator">Large number of AIDs (Emulator)</string>
+    <string name="nfc_hce_large_num_aids_reader">Large number of AIDs (Reader)</string>
+    <string name="nfc_hce_large_num_aids_help">This test tries to register a large number of different AIDs, to make sure there are no limitations on the maximum amount of HCE apps on the device. Note that this test may take a few seconds to complete; please be patient.</string>
+
     <string name="nfc_hce_payment_prefix_aids_emulator">Payment prefix AIDs (Emulator)</string>
     <string name="nfc_hce_payment_prefix_aids_reader">Payment prefix AIDs (Reader)</string>
     <string name="nfc_hce_payment_prefix_aids_help">This test statically registers prefix AIDs for a payment service.</string>
@@ -779,7 +789,7 @@
         \n\n3. Setup the test scene described in the CameraITS README file, and aim the camera
         at it.
         \n\n4. Run the full ITS test suite on all possible camera Ids.
-        (cd CameraITS; python tools/run_all_tests.py camera=[cameraId]).  Once all
+        (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.
     </string>
@@ -787,8 +797,8 @@
         No camera manager exists!  This test device is in a bad state.
     </string>
     <string name="all_legacy_devices">
-        All cameras on this device are LEGACY mode only - ITS tests will only be applied to LIMITED
-        or better devices.  \'PASS\' button enabled.
+        All cameras on this device are LEGACY mode only - ITS tests are only required on LIMITED
+        or better devices.  Pass.
     </string>
     <string name="its_test_passed">All Camera ITS tests passed.  Pass button enabled!</string>
     <string name="its_test_failed">Some Camera ITS tests failed.</string>
@@ -965,6 +975,14 @@
         itself according to the current rotation of the device.</string>
 
     <string name="test_category_notifications">Notifications</string>
+    <string name="package_priority_test">Notification Package Priority Test</string>
+    <string name="package_priority_info">This test checks that the NotificationManagerService respects
+        user preferences about relative package priorities.
+    </string>
+    <string name="package_priority_high">Find \"%s\" under \"App notifications\" in the \"Sound &amp; notifications\" settings panel, and mark it as having notification priority.</string>
+    <string name="package_priority_default">Find \"%s\" under \"App notifications\" in the \"Sound &amp; notifications\" settings panel, and make sure it has default priority.</string>
+    <string name="package_priority_user_order">Check that ranker respects user priorities.</string>
+
     <string name="attention_test">Notification Attention Management Test</string>
     <string name="attention_info">This test checks that the NotificationManagerService is
         respecting user preferences about notification ranking and filtering.
@@ -1085,6 +1103,20 @@
     <string name="widget_pass">Pass</string>
     <string name="widget_fail">Fail</string>
 
+    <string name="provisioning_byod_nonmarket_allow">Enable non-market apps</string>
+    <string name="provisioning_byod_nonmarket_allow_info">
+        This test verifies that non-market apps can be installed if permitted.\n
+        1. A package installation UI should appear.\n
+        2. Accept the package and verify that it installs.
+    </string>
+
+    <string name="provisioning_byod_nonmarket_deny">Disable non-market apps</string>
+    <string name="provisioning_byod_nonmarket_deny_info">
+        This test verifies that non-market apps cannot be installed unless permitted.\n
+        1. A package installation UI should appear.\n
+        2. Verify that the installation of the package is refused.
+    </string>
+
     <!-- Strings for DeskClock -->
     <string name="deskclock_tests">Alarms and Timers Tests</string>
     <string name="deskclock_tests_info">
@@ -1222,6 +1254,7 @@
         Start by pressing the button on screen and follow instructions to finish the managed provisioning process.
         If your device has not been encrypted before, it will be encrypted and rebooted.
         After the provisioning process completes, return to this page and carry out further verifications.
+        Note: the device will remain encrypted after the test which can only be disabled by factory reset.
     </string>
     <string name="provisioning_byod_start">Start BYOD provisioning flow</string>
     <string name="provisioning_byod_instructions">
@@ -1230,12 +1263,15 @@
         After reboot follow instructions in the notification area to complete the provisioning.\n
         2. After successful provisioning, you should be automatically redirected back to this page.
         Please press through the following verification steps.
-        Allow a few seconds after returning from provisioning, as the profile owner test should automatically pass.
+        Allow a few seconds after returning from provisioning, as the profile owner test should automatically pass.\n
+        \n
+        If the device is being encrypted during step 1, it will remain encrypted After this test.
+        The only way to disable the encryption is to factory reset the device.
     </string>
     <string name="provisioning_byod_profileowner">Profile owner installed</string>
     <string name="provisioning_byod_diskencryption">Full disk encryption enabled</string>
-    <string name="provisioning_byod_profile_visible">Work profile visible in Settings</string>
-    <string name="provisioning_byod_admin_visible">Device administrator visible in Settings</string>
+    <string name="provisioning_byod_profile_visible">Profile-aware accounts settings</string>
+    <string name="provisioning_byod_admin_visible">Profile-aware device administrator settings</string>
     <string name="provisioning_byod_workapps_visible">Badged work apps visible in Launcher</string>
     <string name="provisioning_byod_cross_profile">Open app cross profiles</string>
     <string name="provisioning_byod_cross_profile_app_personal">
@@ -1250,6 +1286,13 @@
         \n
         Verify that you are prompted with the above choices and both options work as intended. Then mark this test accordingly.
     </string>
+    <string name="provisioning_byod_work_notification">Work notification is badged</string>
+    <string name="provisioning_byod_work_notification_instruction">
+        Please press the Go button to trigger a notification.\n
+        \n
+        Verify that the notification is badged (see sample badge below). Then mark this test accordingly.
+    </string>
+    <string name="provisioning_byod_work_notification_title">This is a work notification</string>
     <string name="provisioning_byod_profile_visible_instruction">
         Please press the Go button to open the Settings page.
         Navigate to Accounts and confirm that:\n
@@ -1273,10 +1316,51 @@
         Go to All Apps screen and scroll through it to confirm that:\n
         \n
         - A new set of work apps including CTS Verifier appear in the list.\n
-        - Work badge overlay appears on work app\'s icon.\n
+        - Work badge overlay appears on work app\'s icon (see example icon below).\n
         \n
         Then navigate back to this screen using Recents button.
     </string>
+
+    <string name="provisioning_byod_app_settings">Profile-aware app settings</string>
+    <string name="provisioning_byod_app_settings_instruction">
+        Please press the Go button to open Apps page in settings.\n
+        \n
+        Verify that work profile exists in the dropdown list and selecting it will
+        bring up apps setting in the work profile.\n
+        \n
+        Then use the Back button to return to this test and mark accordingly.
+    </string>
+
+    <string name="provisioning_byod_location_settings">Profile-aware location settings</string>
+    <string name="provisioning_byod_location_settings_instruction">
+        Please press the Go button to open Location page in settings.\n
+        \n
+        Verify that work profile entry exists in the page.\n
+        \n
+        Then use the Back button to return to this test and mark accordingly.
+    </string>
+
+    <string name="provisioning_byod_cred_settings">Profile-aware trusted credential settings</string>
+    <string name="provisioning_byod_cred_settings_instruction">
+        Please press the Go button to open the Security settings.
+        Navigate to "Trusted credentials" and wait for the UI to load.
+        After the list is loaded, confirm that:\n
+        \n
+        The page list credentials for both "Personal" and "Work" profiles.\n
+        \n
+        Then use the Back button to return to this test and mark accordingly.
+    </string>
+
+    <string name="provisioning_byod_print_settings">Profile-aware printing settings</string>
+    <string name="provisioning_byod_print_settings_instruction">
+        Please press the Go button to open the Printing settings.
+        \n
+        Verify that work profile exists in the dropdown list and selecting it will
+        bring up printing setting in the work profile.\n
+        \n
+        Then use the Back button to return to this test and mark accordingly.
+    </string>
+
     <string name="provisioning_byod_no_activity">Cannot communicate with activity in the work profile.</string>
     <string name="provisioning_byod_delete_profile">Initiate deletion of work profile.</string>
     <string name="provisioning_byod_profile_deleted">Work profile deleted.</string>
@@ -1306,7 +1390,7 @@
 
     <string name="js_charging_test">Charging Constraints</string>
     <string name="js_charging_instructions">Verify the behaviour of the JobScheduler API for when the device is on power and unplugged from power. Simply follow the on-screen instructions.</string>
-    <string name="js_charging_description_1">Unplug the phone in order to begin.</string>
+    <string name="js_charging_description_1">Unplug the device in order to begin.</string>
     <string name="js_charging_off_test">Device not charging will not execute a job with a charging constraint.</string>
     <string name="js_charging_on_test">Device when charging will execute a job with a charging constraint.</string>
     <string name="js_charging_description_2">After the above test has passed, plug the device back in to continue. If the above failed, you can simply fail this test.</string>
@@ -1318,7 +1402,103 @@
     <string name="js_any_connectivity_test">Device with no connectivity will not execute a job with an unmetered connectivity constraint.</string>
     <string name="js_no_connectivity_test">Device with no connectivity will still execute a job with no connectivity constraints.</string>
 
+    <!-- String for Live Channels app Tests -->
+
+    <string name="tv_input_discover_test">3rd-party TV input app discoverability test</string>
+    <string name="tv_input_discover_test_info">
+    This test verifies that the pre-loaded Live Channels app is invoked via intents and issues
+    appropriate calls to framework APIs, so that TV input apps work properly with the pre-loaded
+    Live Channels app.
+    </string>
+    <string name="tv_input_discover_test_go_to_setup">
+    Press the \"Launch Live Channels\" button, and set up the newly installed TV input:
+    \"CTS Verifier\".
+    </string>
+    <string name="tv_input_discover_test_verify_setup">
+    Setup activity must have been started.
+    </string>
+    <string name="tv_input_discover_test_tune_to_channel">
+    Press the \"Launch Live Channels\" button, and tune to the channel named \"Dummy\" from
+    \"CTS Verifier\" input. If necessary, configure the channel to be visible.
+    </string>
+    <string name="tv_input_discover_test_verify_tune">
+    Tune command must be called.
+    </string>
+    <string name="tv_input_discover_test_verify_overlay_view">
+    Overlay view must be shown. Verify that there is a text view displaying \"Overlay View Dummy Text\"
+    when you tune to the \"Dummy\" channel.
+    </string>
+
+    <string name="tv_parental_control_test">Live Channels app parental control test</string>
+    <string name="tv_parental_control_test_info">
+    This test verifies that the default Live Channels app invokes proper parental control APIs in
+    the framework.
+    </string>
+    <string name="tv_parental_control_test_turn_on_parental_control">
+    Press the \"Launch Live Channels\" button, and turn on the parental control. If it\'s on
+    already, turn it off and on again.
+    </string>
+    <string name="tv_parental_control_test_verify_receive_broadcast1">
+    TV input service must have received ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED broadcast.
+    </string>
+    <string name="tv_parental_control_test_block_tv_ma">
+    Press the \"Launch Live Channels\" button, and block \"Fake\" rating for \"CtsVerifier\" rating
+    system in the parental control settings. You may have to enable the rating system if it is
+    disabled by default. If it\'s already blocked, unblock, save, and then block again.
+    </string>
+    <string name="tv_parental_control_test_verify_receive_broadcast2">
+    TV input service must have received ACTION_BLOCKED_RATINGS_CHANGED broadcast.
+    </string>
+    <string name="tv_parental_control_test_block_unblock">
+    Press the \"Launch Live Channels\" button; verify that the channel is blocked visually.
+    Try unblock the screen by entering PIN; verify that it\'s unblocked visually.
+    </string>
+
+    <string name="tv_launch_tv_app">Launch Live Channels</string>
+    <string name="tv_channel_not_found">
+    CtsVerifier channel is not set up. Please set up before proceeding.
+    </string>
+
+    <string name="tv_multiple_tracks_test">Live Channels app multiple tracks / subtitle test</string>
+    <string name="tv_multiple_tracks_test_info">
+    This test verifies that the default Live Channels app invokes proper mulitple tracks / subtitle
+    APIs in the framework.
+    </string>
+    <string name="tv_multiple_tracks_test_select_subtitle">
+    Press the \"Launch Live Channels\" button. Verify that the closed caption is off by default.
+    Set closed caption to English.
+    </string>
+    <string name="tv_multiple_tracks_test_verify_set_caption_enabled">
+    Caption should be enabled.
+    </string>
+    <string name="tv_multiple_tracks_test_verify_select_subtitle">
+    The English subtitle track should be selected.
+    </string>
+    <string name="tv_multiple_tracks_test_select_audio">
+    Press the \"Launch Live Channels\" button. Verify that the audio track is English by default.
+    Select Spanish audio track.
+    </string>
+    <string name="tv_multiple_tracks_test_verify_select_audio">
+    The Spanish audio track should be selected.
+    </string>
+
+    <string name="overlay_view_text">Overlay View Dummy Text</string>
+    <string name="fake_rating">Fake</string>
+
     <!-- A list of fully-qualified test classes that should not be run. -->
     <string-array name="disabled_tests" />
 
+    <!-- Strings for screen pinning test -->
+    <string name="screen_pinning_test">Screen Pinning Test</string>
+    <string name="screen_pin_instructions">Pressing next will prompt you to enter screen pinning, allow this app to enter screen pinning.</string>
+    <string name="screen_pin_check_pinned">Press Next to verify the app is pinned.</string>
+    <string name="screen_pin_no_exit">Try to leave the app without unpinning the screen. Press next once you have verified you cannot leave.</string>
+    <string name="screen_pin_exit">Use interactions defined by your device to unpin such as long pressing the back and overview button, then press next.</string>
+    <string name="screen_pinning_done">All tests completed successfully.</string>
+
+    <string name="error_screen_no_longer_pinned">The screen was no longer pinned.</string>
+    <string name="error_screen_already_pinned">Cannot start the test with the screen already pinned.</string>
+    <string name="error_screen_pinning_did_not_start">Screen was not pinned.</string>
+    <string name="error_screen_pinning_did_not_exit">Screen was not unpinned.</string>
+    <string name="error_screen_pinning_couldnt_exit">Could not exit screen pinning through API.</string>
 </resources>
diff --git a/apps/CtsVerifier/res/xml/mock_content_rating_systems.xml b/apps/CtsVerifier/res/xml/mock_content_rating_systems.xml
new file mode 100644
index 0000000..245d7f5
--- /dev/null
+++ b/apps/CtsVerifier/res/xml/mock_content_rating_systems.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright 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.
+-->
+
+<rating-system-definitions xmlns:android="http://schemas.android.com/apk/res/android"
+    android:versionCode="1">
+    <rating-system-definition android:name="CTS_VERIFIER"
+        android:title="CtsVerifier"
+        android:description="@string/app_name">
+        <rating-definition android:name="MOCK_FAKE"
+            android:title="Fake"
+            android:contentAgeHint="0"
+            android:description="@string/fake_rating" />
+    </rating-system-definition>
+</rating-system-definitions>
diff --git a/apps/CtsVerifier/res/xml/mock_tv_input_service.xml b/apps/CtsVerifier/res/xml/mock_tv_input_service.xml
new file mode 100644
index 0000000..1a2cf86
--- /dev/null
+++ b/apps/CtsVerifier/res/xml/mock_tv_input_service.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright 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.
+-->
+
+<tv-input xmlns:android="http://schemas.android.com/apk/res/android"
+    android:setupActivity="com.android.cts.verifier.tv.MockTvInputSetupActivity"
+    android:settingsActivity="com.android.cts.verifier.tv.MockTvInputSettingsActivity" />
diff --git a/apps/CtsVerifier/src/android/support/wearable/view/BoxInsetLayout.java b/apps/CtsVerifier/src/android/support/wearable/view/BoxInsetLayout.java
index 95bac11..77d6a21 100644
--- a/apps/CtsVerifier/src/android/support/wearable/view/BoxInsetLayout.java
+++ b/apps/CtsVerifier/src/android/support/wearable/view/BoxInsetLayout.java
@@ -18,6 +18,8 @@
 
 import com.android.cts.verifier.R;
 
+import android.annotation.TargetApi;
+import android.os.Build;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Rect;
@@ -38,6 +40,7 @@
  * The {@code layout_box} attribute is ignored on a device with a rectangular
  * screen.
  */
+@TargetApi(Build.VERSION_CODES.KITKAT_WATCH)
 public class BoxInsetLayout extends FrameLayout {
 
     private static float FACTOR = 0.146467f; //(1 - sqrt(2)/2)/2
@@ -81,10 +84,10 @@
             requestLayout();
         }
         mInsets.set(
-                insets.getSystemWindowInsetLeft(),
-                insets.getSystemWindowInsetTop(),
-                insets.getSystemWindowInsetRight(),
-                insets.getSystemWindowInsetBottom());
+            insets.getSystemWindowInsetLeft(),
+            insets.getSystemWindowInsetTop(),
+            insets.getSystemWindowInsetRight(),
+            insets.getSystemWindowInsetBottom());
         return insets;
     }
 
@@ -187,9 +190,9 @@
             int totalMargin = 0;
             // BoxInset is a padding. Ignore margin when we want to do BoxInset.
             if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_LEFT) != 0)) {
-                totalPadding = boxInset;
+                totalPadding += boxInset;
             } else {
-                totalMargin = plwf + lp.leftMargin;
+                totalMargin += plwf + lp.leftMargin;
             }
             if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_RIGHT) != 0)) {
                 totalPadding += boxInset;
@@ -206,10 +209,12 @@
             }
 
             // adjust height
+            totalPadding = 0;
+            totalMargin = 0;
             if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_TOP) != 0)) {
-                totalPadding = boxInset;
+                totalPadding += boxInset;
             } else {
-                totalMargin = ptwf + lp.topMargin;
+                totalMargin += ptwf + lp.topMargin;
             }
             if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_BOTTOM) != 0)) {
                 totalPadding += boxInset;
@@ -236,7 +241,7 @@
     }
 
     private void layoutBoxChildren(int left, int top, int right, int bottom,
-            boolean forceLeftGravity) {
+                                  boolean forceLeftGravity) {
         final int count = getChildCount();
         int boxInset = (int)(FACTOR * Math.max(right - left, bottom - top));
 
@@ -272,55 +277,80 @@
                 int paddingTop = child.getPaddingTop();
                 int paddingBottom = child.getPaddingBottom();
 
-                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
-                    case Gravity.CENTER_HORIZONTAL:
-                        childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
-                                lp.leftMargin - lp.rightMargin;
-                        break;
-                    case Gravity.RIGHT:
-                        if (!forceLeftGravity) {
-                            if (mLastKnownRound
-                                    && ((lp.boxedEdges & LayoutParams.BOX_RIGHT) != 0)) {
-                                paddingRight = boxInset;
-                                childLeft = right - left - width;
-                            } else {
-                                childLeft = parentRight - width - lp.rightMargin;
-                            }
+                // If the child's width is match_parent, we ignore gravity and set boxInset padding
+                // on both sides, with a left position of parentLeft + the child's left margin.
+                if (lp.width == LayoutParams.MATCH_PARENT) {
+                    if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_LEFT) != 0)) {
+                        paddingLeft = boxInset;
+                    }
+                    if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_RIGHT) != 0)) {
+                        paddingRight = boxInset;
+                    }
+                    childLeft = parentLeft + lp.leftMargin;
+                } else {
+                    switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+                        case Gravity.CENTER_HORIZONTAL:
+                            childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
+                                    lp.leftMargin - lp.rightMargin;
                             break;
-                        }
-                    case Gravity.LEFT:
-                    default:
-                        if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_LEFT) != 0)) {
-                            paddingLeft = boxInset;
-                            childLeft = 0;
-                        } else {
-                            childLeft = parentLeft + lp.leftMargin;
-                        }
+                        case Gravity.RIGHT:
+                            if (!forceLeftGravity) {
+                                if (mLastKnownRound
+                                        && ((lp.boxedEdges & LayoutParams.BOX_RIGHT) != 0)) {
+                                    paddingRight = boxInset;
+                                    childLeft = right - left - width;
+                                } else {
+                                    childLeft = parentRight - width - lp.rightMargin;
+                                }
+                                break;
+                            }
+                        case Gravity.LEFT:
+                        default:
+                            if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_LEFT) != 0)) {
+                                paddingLeft = boxInset;
+                                childLeft = 0;
+                            } else {
+                                childLeft = parentLeft + lp.leftMargin;
+                            }
+                    }
                 }
 
-                switch (verticalGravity) {
-                    case Gravity.TOP:
-                        if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_TOP) != 0)) {
-                            paddingTop = boxInset;
-                            childTop = 0;
-                        } else {
+                // If the child's height is match_parent, we ignore gravity and set boxInset padding
+                // on both top and bottom, with a top position of parentTop + the child's top
+                // margin.
+                if (lp.height == LayoutParams.MATCH_PARENT) {
+                    if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_TOP) != 0)) {
+                        paddingTop = boxInset;
+                    }
+                    if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_BOTTOM) != 0)) {
+                        paddingBottom = boxInset;
+                    }
+                    childTop = parentTop + lp.topMargin;
+                } else {
+                    switch (verticalGravity) {
+                        case Gravity.TOP:
+                            if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_TOP) != 0)) {
+                                paddingTop = boxInset;
+                                childTop = 0;
+                            } else {
+                                childTop = parentTop + lp.topMargin;
+                            }
+                            break;
+                        case Gravity.CENTER_VERTICAL:
+                            childTop = parentTop + (parentBottom - parentTop - height) / 2 +
+                                    lp.topMargin - lp.bottomMargin;
+                            break;
+                        case Gravity.BOTTOM:
+                            if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_BOTTOM) != 0)) {
+                                paddingBottom = boxInset;
+                                childTop = bottom - top - height;
+                            } else {
+                                childTop = parentBottom - height - lp.bottomMargin;
+                            }
+                            break;
+                        default:
                             childTop = parentTop + lp.topMargin;
-                        }
-                        break;
-                    case Gravity.CENTER_VERTICAL:
-                        childTop = parentTop + (parentBottom - parentTop - height) / 2 +
-                                lp.topMargin - lp.bottomMargin;
-                        break;
-                    case Gravity.BOTTOM:
-                        if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_BOTTOM) != 0)) {
-                            paddingBottom = boxInset;
-                            childTop = bottom - top - height;
-                        } else {
-                            childTop = parentBottom - height - lp.bottomMargin;
-                        }
-                        break;
-                    default:
-                        childTop = parentTop + lp.topMargin;
+                    }
                 }
 
                 child.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java b/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java
index ebddf4f..6b9316f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java
@@ -105,18 +105,20 @@
 
     private String mTestParent;
 
-    public ManifestTestListAdapter(Context context, String testParent) {
+    public ManifestTestListAdapter(Context context, String testParent, String[] disabledTestArray) {
         super(context);
         mContext = context;
         mTestParent = testParent;
-
-        String[] disabledTestArray = context.getResources().getStringArray(R.array.disabled_tests);
         mDisabledTests = new HashSet<>(disabledTestArray.length);
         for (int i = 0; i < disabledTestArray.length; i++) {
             mDisabledTests.add(disabledTestArray[i]);
         }
     }
 
+    public ManifestTestListAdapter(Context context, String testParent) {
+        this(context, testParent, context.getResources().getStringArray(R.array.disabled_tests));
+    }
+
     @Override
     protected List<TestListItem> getRows() {
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/PassFailButtons.java b/apps/CtsVerifier/src/com/android/cts/verifier/PassFailButtons.java
index ab119bd..5a08558 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/PassFailButtons.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/PassFailButtons.java
@@ -16,6 +16,8 @@
 
 package com.android.cts.verifier;
 
+import com.android.compatibility.common.util.ReportLog;
+
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.content.ContentResolver;
@@ -94,10 +96,18 @@
          * @param passed Whether or not the test passed.
          */
         void setTestResultAndFinish(boolean passed);
+
+        /** @return A {@link ReportLog} that is used to record test metric data. */
+        ReportLog getReportLog();
     }
 
     public static class Activity extends android.app.Activity implements PassFailActivity {
         private WakeLock mWakeLock;
+        private final ReportLog reportLog;
+
+        public Activity() {
+           this.reportLog = new CtsVerifierReportLog();
+        }
 
         @Override
         protected void onResume() {
@@ -149,13 +159,22 @@
 
         @Override
         public void setTestResultAndFinish(boolean passed) {
-            PassFailButtons.setTestResultAndFinishHelper(this, getTestId(), getTestDetails(),
-                    passed);
+            PassFailButtons.setTestResultAndFinishHelper(
+                    this, getTestId(), getTestDetails(), passed, getReportLog());
         }
+
+        @Override
+        public ReportLog getReportLog() { return reportLog; }
     }
 
     public static class ListActivity extends android.app.ListActivity implements PassFailActivity {
 
+        private final ReportLog reportLog;
+
+        public ListActivity() {
+            this.reportLog = new CtsVerifierReportLog();
+        }
+
         @Override
         public void setPassFailButtonClickListeners() {
             setPassFailClickListeners(this);
@@ -188,14 +207,23 @@
 
         @Override
         public void setTestResultAndFinish(boolean passed) {
-            PassFailButtons.setTestResultAndFinishHelper(this, getTestId(), getTestDetails(),
-                    passed);
+            PassFailButtons.setTestResultAndFinishHelper(
+                    this, getTestId(), getTestDetails(), passed, getReportLog());
         }
+
+        @Override
+        public ReportLog getReportLog() { return reportLog; }
     }
 
     public static class TestListActivity extends AbstractTestListActivity
             implements PassFailActivity {
 
+        private final ReportLog reportLog;
+
+        public TestListActivity() {
+            this.reportLog = new CtsVerifierReportLog();
+        }
+
         @Override
         public void setPassFailButtonClickListeners() {
             setPassFailClickListeners(this);
@@ -228,9 +256,12 @@
 
         @Override
         public void setTestResultAndFinish(boolean passed) {
-            PassFailButtons.setTestResultAndFinishHelper(this, getTestId(), getTestDetails(),
-                    passed);
+            PassFailButtons.setTestResultAndFinishHelper(
+                    this, getTestId(), getTestDetails(), passed, getReportLog());
         }
+
+        @Override
+        public ReportLog getReportLog() { return reportLog; }
     }
 
     private static <T extends android.app.Activity & PassFailActivity>
@@ -239,7 +270,7 @@
             @Override
             public void onClick(View target) {
                 setTestResultAndFinish(activity, activity.getTestId(), activity.getTestDetails(),
-                        target);
+                        activity.getReportLog(), target);
             }
         };
 
@@ -366,7 +397,7 @@
 
     /** Set the test result corresponding to the button clicked and finish the activity. */
     private static void setTestResultAndFinish(android.app.Activity activity, String testId,
-            String testDetails, View target) {
+            String testDetails, ReportLog reportLog, View target) {
         boolean passed;
         switch (target.getId()) {
             case R.id.pass_button:
@@ -378,16 +409,16 @@
             default:
                 throw new IllegalArgumentException("Unknown id: " + target.getId());
         }
-        setTestResultAndFinishHelper(activity, testId, testDetails, passed);
+        setTestResultAndFinishHelper(activity, testId, testDetails, passed, reportLog);
     }
 
     /** Set the test result and finish the activity. */
     private static void setTestResultAndFinishHelper(android.app.Activity activity, String testId,
-            String testDetails, boolean passed) {
+            String testDetails, boolean passed, ReportLog reportLog) {
         if (passed) {
-            TestResult.setPassedResult(activity, testId, testDetails);
+            TestResult.setPassedResult(activity, testId, testDetails, reportLog);
         } else {
-            TestResult.setFailedResult(activity, testId, testDetails);
+            TestResult.setFailedResult(activity, testId, testDetails, reportLog);
         }
 
         activity.finish();
@@ -396,4 +427,8 @@
     private static ImageButton getPassButtonView(android.app.Activity activity) {
         return (ImageButton) activity.findViewById(R.id.pass_button);
     }
+
+    public static class CtsVerifierReportLog extends ReportLog {
+
+    }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java
index 43d300a..8cfc6df 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java
@@ -23,19 +23,42 @@
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
+import android.view.View;
+import android.view.Window;
 import android.widget.Toast;
 
 import java.io.IOException;
 
 /** Top-level {@link ListActivity} for launching tests and managing results. */
-public class TestListActivity extends AbstractTestListActivity {
+public class TestListActivity extends AbstractTestListActivity implements View.OnClickListener {
 
     private static final String TAG = TestListActivity.class.getSimpleName();
 
     @Override
+    public void onClick (View v) {
+        handleMenuItemSelected(v.getId());
+    }
+
+    @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+
+        if (!isTaskRoot()) {
+            finish();
+        }
+
         setTitle(getString(R.string.title_version, Version.getVersionName(this)));
+
+        if (!getWindow().hasFeature(Window.FEATURE_ACTION_BAR)) {
+            View footer = getLayoutInflater().inflate(R.layout.test_list_footer, null);
+
+            footer.findViewById(R.id.clear).setOnClickListener(this);
+            footer.findViewById(R.id.view).setOnClickListener(this);
+            footer.findViewById(R.id.export).setOnClickListener(this);
+
+            getListView().addFooterView(footer);
+        }
+
         setTestListAdapter(new ManifestTestListAdapter(this, null));
     }
 
@@ -48,22 +71,7 @@
 
     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
-        switch (item.getItemId()) {
-            case R.id.clear:
-                handleClearItemSelected();
-                return true;
-
-            case R.id.view:
-                handleViewItemSelected();
-                return true;
-
-            case R.id.export:
-                handleExportItemSelected();
-                return true;
-
-            default:
-                return super.onOptionsItemSelected(item);
-        }
+        return handleMenuItemSelected(item.getItemId()) ? true : super.onOptionsItemSelected(item);
     }
 
     private void handleClearItemSelected() {
@@ -86,4 +94,23 @@
     private void handleExportItemSelected() {
         new ReportExporter(this, mAdapter).execute();
     }
+
+    private boolean handleMenuItemSelected(int id) {
+        switch (id) {
+            case R.id.clear:
+                handleClearItemSelected();
+                return true;
+
+            case R.id.view:
+                handleViewItemSelected();
+                return true;
+
+            case R.id.export:
+                handleExportItemSelected();
+                return true;
+
+            default:
+                return false;
+        }
+    }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestListAdapter.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestListAdapter.java
index afe3a73..2160902 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestListAdapter.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestListAdapter.java
@@ -16,6 +16,8 @@
 
 package com.android.cts.verifier;
 
+import com.android.compatibility.common.util.ReportLog;
+
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
@@ -30,6 +32,9 @@
 import android.widget.ListView;
 import android.widget.TextView;
 
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -66,6 +71,9 @@
     /** Map from test name to test details. */
     private final Map<String, String> mTestDetails = new HashMap<String, String>();
 
+    /** Map from test name to {@link ReportLog}. */
+    private final Map<String, ReportLog> mReportLogs = new HashMap<String, ReportLog>();
+
     private final LayoutInflater mLayoutInflater;
 
     /** {@link ListView} row that is either a test category header or a test. */
@@ -168,7 +176,7 @@
 
     public void setTestResult(TestResult testResult) {
         new SetTestResultTask(testResult.getName(), testResult.getResult(),
-                testResult.getDetails()).execute();
+                testResult.getDetails(), testResult.getReportLog()).execute();
     }
 
     class RefreshTestResultsTask extends AsyncTask<Void, Void, RefreshResult> {
@@ -187,6 +195,8 @@
             mTestResults.putAll(result.mResults);
             mTestDetails.clear();
             mTestDetails.putAll(result.mDetails);
+            mReportLogs.clear();
+            mReportLogs.putAll(result.mReportLogs);
             notifyDataSetChanged();
         }
     }
@@ -195,12 +205,17 @@
         List<TestListItem> mItems;
         Map<String, Integer> mResults;
         Map<String, String> mDetails;
+        Map<String, ReportLog> mReportLogs;
 
-        RefreshResult(List<TestListItem> items, Map<String, Integer> results,
-                Map<String, String> details) {
+        RefreshResult(
+                List<TestListItem> items,
+                Map<String, Integer> results,
+                Map<String, String> details,
+                Map<String, ReportLog> reportLogs) {
             mItems = items;
             mResults = results;
             mDetails = details;
+            mReportLogs = reportLogs;
         }
     }
 
@@ -211,11 +226,13 @@
         TestResultsProvider.COLUMN_TEST_NAME,
         TestResultsProvider.COLUMN_TEST_RESULT,
         TestResultsProvider.COLUMN_TEST_DETAILS,
+        TestResultsProvider.COLUMN_TEST_METRICS,
     };
 
     RefreshResult getRefreshResults(List<TestListItem> items) {
         Map<String, Integer> results = new HashMap<String, Integer>();
         Map<String, String> details = new HashMap<String, String>();
+        Map<String, ReportLog> reportLogs = new HashMap<String, ReportLog>();
         ContentResolver resolver = mContext.getContentResolver();
         Cursor cursor = null;
         try {
@@ -226,8 +243,10 @@
                     String testName = cursor.getString(1);
                     int testResult = cursor.getInt(2);
                     String testDetails = cursor.getString(3);
+                    ReportLog reportLog = (ReportLog) deserialize(cursor.getBlob(4));
                     results.put(testName, testResult);
                     details.put(testName, testDetails);
+                    reportLogs.put(testName, reportLog);
                 } while (cursor.moveToNext());
             }
         } finally {
@@ -235,7 +254,7 @@
                 cursor.close();
             }
         }
-        return new RefreshResult(items, results, details);
+        return new RefreshResult(items, results, details, reportLogs);
     }
 
     class ClearTestResultsTask extends AsyncTask<Void, Void, Void> {
@@ -256,15 +275,22 @@
 
         private final String mDetails;
 
-        SetTestResultTask(String testName, int result, String details) {
+        private final ReportLog mReportLog;
+
+        SetTestResultTask(
+                String testName,
+                int result,
+                String details,
+                ReportLog reportLog) {
             mTestName = testName;
             mResult = result;
             mDetails = details;
+            mReportLog = reportLog;
         }
 
         @Override
         protected Void doInBackground(Void... params) {
-            TestResultsProvider.setTestResult(mContext, mTestName, mResult, mDetails);
+            TestResultsProvider.setTestResult(mContext, mTestName, mResult, mDetails, mReportLog);
             return null;
         }
     }
@@ -332,6 +358,13 @@
                 : null;
     }
 
+    public ReportLog getReportLog(int position) {
+        TestListItem item = getItem(position);
+        return mReportLogs.containsKey(item.testName)
+                ? mReportLogs.get(item.testName)
+                : null;
+    }
+
     public boolean allTestsPassed() {
         for (TestListItem item : mRows) {
             if (item.isTest() && (!mTestResults.containsKey(item.testName)
@@ -400,4 +433,29 @@
 
         }
     }
+
+    private static Object deserialize(byte[] bytes) {
+        if (bytes == null || bytes.length == 0) {
+            return null;
+        }
+        ByteArrayInputStream byteStream = new ByteArrayInputStream(bytes);
+        ObjectInputStream objectInput = null;
+        try {
+            objectInput = new ObjectInputStream(byteStream);
+            return objectInput.readObject();
+        } catch (IOException e) {
+            return null;
+        } catch (ClassNotFoundException e) {
+            return null;
+        } finally {
+            try {
+                if (objectInput != null) {
+                    objectInput.close();
+                }
+                byteStream.close();
+            } catch (IOException e) {
+                // Ignore close exception.
+            }
+        }
+    }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestResult.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestResult.java
index 68513ac..d8a675c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestResult.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestResult.java
@@ -16,6 +16,8 @@
 
 package com.android.cts.verifier;
 
+import com.android.compatibility.common.util.ReportLog;
+
 import android.app.Activity;
 import android.content.Intent;
 
@@ -35,29 +37,44 @@
     private static final String TEST_NAME = "name";
     private static final String TEST_RESULT = "result";
     private static final String TEST_DETAILS = "details";
+    private static final String TEST_METRICS = "metrics";
 
     private final String mName;
     private final int mResult;
     private final String mDetails;
+    private final ReportLog mReportLog;
 
     /** Sets the test activity's result to pass. */
     public static void setPassedResult(Activity activity, String testId, String testDetails) {
+        setPassedResult(activity, testId, testDetails, null /*reportLog*/);
+    }
+
+    /** Sets the test activity's result to pass including a test report log result. */
+    public static void setPassedResult(Activity activity, String testId, String testDetails,
+            ReportLog reportLog) {
         activity.setResult(Activity.RESULT_OK, createResult(activity, TEST_RESULT_PASSED, testId,
-                testDetails));
+                testDetails, reportLog));
     }
 
     /** Sets the test activity's result to failed. */
     public static void setFailedResult(Activity activity, String testId, String testDetails) {
+        setFailedResult(activity, testId, testDetails, null /*reportLog*/);
+    }
+
+    /** Sets the test activity's result to failed including a test report log result. */
+    public static void setFailedResult(Activity activity, String testId, String testDetails,
+            ReportLog reportLog) {
         activity.setResult(Activity.RESULT_OK, createResult(activity, TEST_RESULT_FAILED, testId,
-                testDetails));
+                testDetails, reportLog));
     }
 
     private static Intent createResult(Activity activity, int testResult, String testName,
-            String testDetails) {
+            String testDetails, ReportLog reportLog) {
         Intent data = new Intent(activity, activity.getClass());
         data.putExtra(TEST_NAME, testName);
         data.putExtra(TEST_RESULT, testResult);
         data.putExtra(TEST_DETAILS, testDetails);
+        data.putExtra(TEST_METRICS, reportLog);
         return data;
     }
 
@@ -69,13 +86,16 @@
         String name = data.getStringExtra(TEST_NAME);
         int result = data.getIntExtra(TEST_RESULT, TEST_RESULT_NOT_EXECUTED);
         String details = data.getStringExtra(TEST_DETAILS);
-        return new TestResult(name, result, details);
+        ReportLog reportLog = (ReportLog) data.getSerializableExtra(TEST_METRICS);
+        return new TestResult(name, result, details, reportLog);
     }
 
-    private TestResult(String name, int result, String details) {
+    private TestResult(
+            String name, int result, String details, ReportLog reportLog) {
         this.mName = name;
         this.mResult = result;
         this.mDetails = details;
+        this.mReportLog = reportLog;
     }
 
     /** Return the name of the test like "com.android.cts.verifier.foo.FooTest" */
@@ -92,4 +112,9 @@
     public String getDetails() {
         return mDetails;
     }
+
+    /** @return the {@link ReportLog} or null if not set */
+    public ReportLog getReportLog() {
+        return mReportLog;
+    }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsBackupHelper.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsBackupHelper.java
index e4cd24a..45e528f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsBackupHelper.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsBackupHelper.java
@@ -59,6 +59,7 @@
             int resultIndex = cursor.getColumnIndex(TestResultsProvider.COLUMN_TEST_RESULT);
             int infoSeenIndex = cursor.getColumnIndex(TestResultsProvider.COLUMN_TEST_INFO_SEEN);
             int detailsIndex = cursor.getColumnIndex(TestResultsProvider.COLUMN_TEST_DETAILS);
+            int metricsIndex = cursor.getColumnIndex(TestResultsProvider.COLUMN_TEST_METRICS);
 
             ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
             DataOutputStream dataOutput = new DataOutputStream(byteOutput);
@@ -69,11 +70,16 @@
                 int result = cursor.getInt(resultIndex);
                 int infoSeen = cursor.getInt(infoSeenIndex);
                 String details = cursor.getString(detailsIndex);
+                byte[] metricsData = cursor.getBlob(metricsIndex);
 
                 dataOutput.writeUTF(name);
                 dataOutput.writeInt(result);
                 dataOutput.writeInt(infoSeen);
                 dataOutput.writeUTF(details != null ? details : "");
+                dataOutput.writeInt(metricsData.length);
+                if (metricsData.length > 0) {
+                    dataOutput.write(metricsData);
+                }
             }
 
             byte[] rawBytes = byteOutput.toByteArray();
@@ -106,12 +112,19 @@
                     int result = dataInput.readInt();
                     int infoSeen = dataInput.readInt();
                     String details = dataInput.readUTF();
+                    int metricsDataSize = dataInput.readInt();
 
                     values[i] = new ContentValues();
                     values[i].put(TestResultsProvider.COLUMN_TEST_NAME, name);
                     values[i].put(TestResultsProvider.COLUMN_TEST_RESULT, result);
                     values[i].put(TestResultsProvider.COLUMN_TEST_INFO_SEEN, infoSeen);
                     values[i].put(TestResultsProvider.COLUMN_TEST_DETAILS, details);
+
+                    if (metricsDataSize > 0) {
+                        byte[] metrics = new byte[metricsDataSize];
+                        dataInput.readFully(metrics);
+                        values[i].put(TestResultsProvider.COLUMN_TEST_METRICS, metrics);
+                    }
                 }
 
                 ContentResolver resolver = mContext.getContentResolver();
@@ -127,7 +140,7 @@
 
     private void failBackupTest() {
         TestResultsProvider.setTestResult(mContext, BackupTestActivity.class.getName(),
-                TestResult.TEST_RESULT_FAILED, null);
+                TestResult.TEST_RESULT_FAILED, null /*testDetails*/, null /*testMetrics*/);
     }
 
     @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsProvider.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsProvider.java
index df05519..a9f672e 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsProvider.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsProvider.java
@@ -16,6 +16,8 @@
 
 package com.android.cts.verifier;
 
+import com.android.compatibility.common.util.ReportLog;
+
 import android.app.backup.BackupManager;
 import android.content.ContentProvider;
 import android.content.ContentResolver;
@@ -28,6 +30,10 @@
 import android.database.sqlite.SQLiteQueryBuilder;
 import android.net.Uri;
 
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+
 /** {@link ContentProvider} that provides read and write access to the test results. */
 public class TestResultsProvider extends ContentProvider {
 
@@ -56,6 +62,9 @@
     /** String containing the test's details. */
     static final String COLUMN_TEST_DETAILS = "testdetails";
 
+    /** ReportLog containing the test result metrics. */
+    static final String COLUMN_TEST_METRICS = "testmetrics";
+
     private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
     private static final int RESULTS_ALL = 1;
     private static final int RESULTS_ID = 2;
@@ -96,7 +105,8 @@
                     + COLUMN_TEST_NAME + " TEXT, "
                     + COLUMN_TEST_RESULT + " INTEGER,"
                     + COLUMN_TEST_INFO_SEEN + " INTEGER DEFAULT 0,"
-                    + COLUMN_TEST_DETAILS + " TEXT);");
+                    + COLUMN_TEST_DETAILS + " TEXT,"
+                    + COLUMN_TEST_METRICS + " BLOB);");
         }
 
         @Override
@@ -202,11 +212,12 @@
     }
 
     static void setTestResult(Context context, String testName, int testResult,
-            String testDetails) {
+            String testDetails, ReportLog reportLog) {
         ContentValues values = new ContentValues(2);
         values.put(TestResultsProvider.COLUMN_TEST_RESULT, testResult);
         values.put(TestResultsProvider.COLUMN_TEST_NAME, testName);
         values.put(TestResultsProvider.COLUMN_TEST_DETAILS, testDetails);
+        values.put(TestResultsProvider.COLUMN_TEST_METRICS, serialize(reportLog));
 
         ContentResolver resolver = context.getContentResolver();
         int numUpdated = resolver.update(TestResultsProvider.RESULTS_CONTENT_URI, values,
@@ -218,4 +229,24 @@
         }
     }
 
+    private static byte[] serialize(Object o) {
+        ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
+        ObjectOutputStream objectOutput = null;
+        try {
+            objectOutput = new ObjectOutputStream(byteStream);
+            objectOutput.writeObject(o);
+            return byteStream.toByteArray();
+        } catch (IOException e) {
+            return null;
+        } finally {
+            try {
+                if (objectOutput != null) {
+                    objectOutput.close();
+                }
+                byteStream.close();
+            } catch (IOException e) {
+                // Ignore close exception.
+            }
+        }
+    }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java
index e40b428..dc2502c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java
@@ -16,6 +16,8 @@
 
 package com.android.cts.verifier;
 
+import com.android.compatibility.common.util.MetricsXmlSerializer;
+import com.android.compatibility.common.util.ReportLog;
 import com.android.cts.verifier.TestListAdapter.TestListItem;
 
 import org.xmlpull.v1.XmlSerializer;
@@ -128,6 +130,12 @@
                     xml.endTag(null, TEST_DETAILS_TAG);
                 }
 
+                ReportLog reportLog = mAdapter.getReportLog(i);
+                if (reportLog != null) {
+                    MetricsXmlSerializer metricsXmlSerializer = new MetricsXmlSerializer(xml);
+                    metricsXmlSerializer.serialize(reportLog);
+                }
+
                 xml.endTag(null, TEST_TAG);
             }
         }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleAdvertiserService.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleAdvertiserService.java
index 281b2e8..b4a6416 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleAdvertiserService.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleAdvertiserService.java
@@ -89,8 +89,11 @@
     public static final byte MANUFACTURER_TEST_ID = (byte)0x07;
     public static final byte[] PRIVACY_MAC_DATA = new byte[]{3, 1, 4};
     public static final byte[] PRIVACY_RESPONSE = new byte[]{9, 2, 6};
-    public static final byte[] POWER_LEVEL_DATA = new byte[]{1, 5, 0};
-    public static final byte[] POWER_LEVEL_MASK = new byte[]{1, 1, 0};
+    public static final byte[] POWER_LEVEL_DATA = new byte[]{1, 5, 0, 0, 0,
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0};  // 15 bytes
+    public static final byte[] POWER_LEVEL_MASK = new byte[]{1, 1, 0, 0, 0,
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0};  // 15 bytes
+    public static final int POWER_LEVEL_DATA_LENGTH = 15;
     public static final byte[] SCANNABLE_DATA = new byte[]{5, 3, 5};
     public static final byte[] UNSCANNABLE_DATA = new byte[]{8, 9, 7};
 
@@ -221,8 +224,26 @@
                 break;
             case COMMAND_START_POWER_LEVEL:
                 for (int t : mPowerLevel) {
-                    AdvertiseData d =
-                            generateAdvertiseData(POWER_LEVEL_UUID, new byte[]{1, 5, (byte)t});
+                    // Service data:
+                    //    field overhead = 2 bytes
+                    //    uuid = 2 bytes
+                    //    data = 15 bytes
+                    // Manufacturer data:
+                    //    field overhead = 2 bytes
+                    //    Specific data length = 2 bytes
+                    //    data length = 2 bytes
+                    // Include power level:
+                    //    field overhead = 2 bytes
+                    //    1 byte
+                    // Connectable flag: 3 bytes (0 byte for Android 5.1+)
+                    // SUM = 31 bytes
+                    byte[] dataBytes = new byte[POWER_LEVEL_DATA_LENGTH];
+                    dataBytes[0] = 0x01;
+                    dataBytes[1] = 0x05;
+                    for (int i = 2; i < POWER_LEVEL_DATA_LENGTH; i++) {
+                        dataBytes[i] = (byte)t;
+                    }
+                    AdvertiseData d = generateAdvertiseData(POWER_LEVEL_UUID, dataBytes);
                     AdvertiseSettings settings = generateSetting(t);
                     mAdvertiser.startAdvertising(settings, d, mPowerCallback.get(t));
                 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleAdvertiserTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleAdvertiserTestActivity.java
index 637ef71..64c50bc 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleAdvertiserTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleAdvertiserTestActivity.java
@@ -20,8 +20,12 @@
 import com.android.cts.verifier.PassFailButtons;
 import com.android.cts.verifier.R;
 
+import android.bluetooth.BluetoothAdapter;
 import android.os.Bundle;
 
+import java.util.ArrayList;
+import java.util.List;
+
 public class BleAdvertiserTestActivity extends PassFailButtons.TestListActivity {
 
     @Override
@@ -31,6 +35,14 @@
         setPassFailButtonClickListeners();
         setInfoResources(R.string.ble_advertiser_test_name, R.string.ble_advertiser_test_info, -1);
 
-        setTestListAdapter(new ManifestTestListAdapter(this, getClass().getName()));
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        List<String> disabledTest = new ArrayList<String>();
+        if (adapter == null || !adapter.isOffloadedFilteringSupported()) {
+            disabledTest.add(
+                    "com.android.cts.verifier.bluetooth.BleAdvertiserHardwareScanFilterActivity.");
+        }
+
+        setTestListAdapter(new ManifestTestListAdapter(this, getClass().getName(),
+                disabledTest.toArray(new String[disabledTest.size()])));
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientCharacteristicActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientCharacteristicActivity.java
deleted file mode 100644
index 1e1941f..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientCharacteristicActivity.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2013 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.verifier.bluetooth;
-
-public class BleClientCharacteristicActivity extends BleReadWriteActivity {
-    public BleClientCharacteristicActivity() {
-        super(BleReadWriteActivity.CHARACTERISTIC);
-    }
-}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientConnectActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientConnectActivity.java
deleted file mode 100755
index fb351b1..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientConnectActivity.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2013 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.verifier.bluetooth;
-
-import com.android.cts.verifier.PassFailButtons;
-import com.android.cts.verifier.R;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Bundle;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.Toast;
-
-public class BleClientConnectActivity extends PassFailButtons.Activity {
-
-    private EditText mEditText;
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.ble_client_connect);
-        setPassFailButtonClickListeners();
-        setInfoResources(R.string.ble_client_connect_name,
-                         R.string.ble_client_send_connect_info, -1);
-        getPassButton().setEnabled(false);
-
-        mEditText = (EditText) findViewById(R.id.ble_address);
-
-        ((Button) findViewById(R.id.ble_connect)).setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                String address = mEditText.getText().toString();
-                if (!BluetoothAdapter.checkBluetoothAddress(address)) {
-                    showMessage("Invalid bluetooth address.");
-                } else {
-                    Intent intent = new Intent(BleClientConnectActivity.this,
-                                               BleClientService.class);
-                    intent.putExtra(BleClientService.EXTRA_COMMAND,
-                                    BleClientService.COMMAND_CONNECT);
-                    intent.putExtra(BluetoothDevice.EXTRA_DEVICE, address);
-                    startService(intent);
-                }
-            }
-        });
-
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(BleClientService.BLE_BLUETOOTH_CONNECTED);
-        registerReceiver(onBroadcast, filter);
-    }
-
-    @Override
-    protected void onDestroy(){
-        super.onDestroy();
-        unregisterReceiver(onBroadcast);
-    }
-
-    private void showMessage(String msg) {
-        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
-    }
-
-    private BroadcastReceiver onBroadcast = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            showMessage("Bluetooth LE connected");
-            getPassButton().setEnabled(true);
-        }
-    };
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientDescriptorActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientDescriptorActivity.java
deleted file mode 100644
index ab2229a..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientDescriptorActivity.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2013 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.verifier.bluetooth;
-
-public class BleClientDescriptorActivity extends BleReadWriteActivity {
-    public BleClientDescriptorActivity() {
-        super(BleReadWriteActivity.DESCRIPTOR);
-    }
-}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientDisconnectActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientDisconnectActivity.java
deleted file mode 100644
index 083d327..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientDisconnectActivity.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2013 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.verifier.bluetooth;
-
-public class BleClientDisconnectActivity extends BleButtonActivity {
-    public BleClientDisconnectActivity() {
-        super(BleButtonActivity.DISCONNECT);
-    }
-}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientService.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientService.java
index 556ad06..10f862d 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientService.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientService.java
@@ -16,6 +16,7 @@
 
 package com.android.cts.verifier.bluetooth;
 
+import java.util.Arrays;
 import java.util.UUID;
 import java.util.List;
 
@@ -29,10 +30,16 @@
 import android.bluetooth.BluetoothGattService;
 import android.bluetooth.BluetoothManager;
 import android.bluetooth.BluetoothProfile;
+import android.bluetooth.le.BluetoothLeScanner;
+import android.bluetooth.le.ScanCallback;
+import android.bluetooth.le.ScanFilter;
+import android.bluetooth.le.ScanResult;
+import android.bluetooth.le.ScanSettings;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.ParcelUuid;
 import android.util.Log;
 import android.widget.Toast;
 
@@ -53,6 +60,8 @@
     public static final int COMMAND_BEGIN_WRITE = 9;
     public static final int COMMAND_EXECUTE_WRITE = 10;
     public static final int COMMAND_ABORT_RELIABLE = 11;
+    public static final int COMMAND_SCAN_START = 12;
+    public static final int COMMAND_SCAN_STOP = 13;
 
     public static final String BLE_BLUETOOTH_CONNECTED =
             "com.android.cts.verifier.bluetooth.BLE_BLUETOOTH_CONNECTED";
@@ -87,6 +96,8 @@
             "com.android.cts.verifier.bluetooth.EXTRA_DESCRIPTOR_VALUE";
     public static final String EXTRA_RSSI_VALUE =
             "com.android.cts.verifier.bluetooth.EXTRA_RSSI_VALUE";
+    public static final String EXTRA_ERROR_MESSAGE =
+            "com.android.cts.verifier.bluetooth.EXTRA_ERROR_MESSAGE";
 
     private static final UUID SERVICE_UUID =
             UUID.fromString("00009999-0000-1000-8000-00805f9b34fb");
@@ -97,11 +108,15 @@
     private static final UUID DESCRIPTOR_UUID =
             UUID.fromString("00009996-0000-1000-8000-00805f9b34fb");
 
+    private static final String WRITE_VALUE = "TEST";
+
     private BluetoothManager mBluetoothManager;
     private BluetoothAdapter mBluetoothAdapter;
     private BluetoothDevice mDevice;
     private BluetoothGatt mBluetoothGatt;
+    private BluetoothLeScanner mScanner;
     private Handler mHandler;
+    private Context mContext;
 
     @Override
     public void onCreate() {
@@ -109,12 +124,14 @@
 
         mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
         mBluetoothAdapter = mBluetoothManager.getAdapter();
+        mScanner = mBluetoothAdapter.getBluetoothLeScanner();
         mHandler = new Handler();
+        mContext = this;
+        startScan();
     }
 
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
-        if (intent != null) handleIntent(intent);
         return START_NOT_STICKY;
     }
 
@@ -128,59 +145,8 @@
         super.onDestroy();
         mBluetoothGatt.disconnect();
         mBluetoothGatt.close();
-    }
-
-    private void handleIntent(Intent intent) {
-        int command = intent.getIntExtra(EXTRA_COMMAND, -1);
-        String address = intent.getStringExtra(BluetoothDevice.EXTRA_DEVICE); // sometimes null
-        String writeValue = intent.getStringExtra(EXTRA_WRITE_VALUE); // sometimes null
-        boolean enable = intent.getBooleanExtra(EXTRA_BOOL, false);
-        BluetoothGattService service;
-        BluetoothGattCharacteristic characteristic;
-        BluetoothGattDescriptor descriptor;
-
-        switch (command) {
-            case COMMAND_CONNECT:
-                mDevice = mBluetoothAdapter.getRemoteDevice(address);
-                mBluetoothGatt = mDevice.connectGatt(this, false, mGattCallbacks);
-                break;
-            case COMMAND_DISCONNECT:
-                if (mBluetoothGatt != null) mBluetoothGatt.disconnect();
-                break;
-            case COMMAND_DISCOVER_SERVICE:
-                if (mBluetoothGatt != null) mBluetoothGatt.discoverServices();
-                break;
-            case COMMAND_READ_RSSI:
-                if (mBluetoothGatt != null) mBluetoothGatt.readRemoteRssi();
-                break;
-            case COMMAND_WRITE_CHARACTERISTIC:
-                writeCharacteristic(writeValue);
-                break;
-            case COMMAND_READ_CHARACTERISTIC:
-                readCharacteristic();
-                break;
-            case COMMAND_WRITE_DESCRIPTOR:
-                writeDescriptor(writeValue);
-                break;
-            case COMMAND_READ_DESCRIPTOR:
-                readDescriptor();
-                break;
-            case COMMAND_SET_NOTIFICATION:
-                setNotification(enable);
-                break;
-            case COMMAND_BEGIN_WRITE:
-                if (mBluetoothGatt != null) mBluetoothGatt.beginReliableWrite();
-                break;
-            case COMMAND_EXECUTE_WRITE:
-                if (mBluetoothGatt != null) mBluetoothGatt.executeReliableWrite();
-                break;
-            case COMMAND_ABORT_RELIABLE:
-                if (mBluetoothGatt != null) mBluetoothGatt.abortReliableWrite(mDevice);
-                break;
-            default:
-                showMessage("Unrecognized command: " + command);
-                break;
-        }
+        mBluetoothGatt = null;
+        stopScan();
     }
 
     private void writeCharacteristic(String writeValue) {
@@ -213,55 +179,69 @@
             mBluetoothGatt.setCharacteristicNotification(characteristic, enable);
     }
 
+    private void notifyError(String message) {
+        showMessage(message);
+    }
+
     private void notifyConnected() {
+        showMessage("BLE connected");
         Intent intent = new Intent(BLE_BLUETOOTH_CONNECTED);
         sendBroadcast(intent);
     }
 
     private void notifyDisconnected() {
+        showMessage("BLE disconnected");
         Intent intent = new Intent(BLE_BLUETOOTH_DISCONNECTED);
         sendBroadcast(intent);
     }
 
     private void notifyServicesDiscovered() {
+        showMessage("Service discovered");
         Intent intent = new Intent(BLE_SERVICES_DISCOVERED);
         sendBroadcast(intent);
     }
 
     private void notifyCharacteristicRead(String value) {
+        showMessage("Characteristic read: " + value);
         Intent intent = new Intent(BLE_CHARACTERISTIC_READ);
         intent.putExtra(EXTRA_CHARACTERISTIC_VALUE, value);
         sendBroadcast(intent);
     }
 
-    private void notifyCharacteristicWrite() {
+    private void notifyCharacteristicWrite(String value) {
+        showMessage("Characteristic write: " + value);
         Intent intent = new Intent(BLE_CHARACTERISTIC_WRITE);
         sendBroadcast(intent);
     }
 
     private void notifyCharacteristicChanged(String value) {
+        showMessage("Characteristic changed: " + value);
         Intent intent = new Intent(BLE_CHARACTERISTIC_CHANGED);
         intent.putExtra(EXTRA_CHARACTERISTIC_VALUE, value);
         sendBroadcast(intent);
     }
 
     private void notifyDescriptorRead(String value) {
+        showMessage("Descriptor read: " + value);
         Intent intent = new Intent(BLE_DESCRIPTOR_READ);
         intent.putExtra(EXTRA_DESCRIPTOR_VALUE, value);
         sendBroadcast(intent);
     }
 
-    private void notifyDescriptorWrite() {
+    private void notifyDescriptorWrite(String value) {
+        showMessage("Descriptor write: " + value);
         Intent intent = new Intent(BLE_DESCRIPTOR_WRITE);
         sendBroadcast(intent);
     }
 
     private void notifyReliableWriteCompleted() {
+        showMessage("Reliable write compelte");
         Intent intent = new Intent(BLE_RELIABLE_WRITE_COMPLETED);
         sendBroadcast(intent);
     }
 
     private void notifyReadRemoteRssi(int rssi) {
+        showMessage("Remote rssi read: " + rssi);
         Intent intent = new Intent(BLE_READ_REMOTE_RSSI);
         intent.putExtra(EXTRA_RSSI_VALUE, rssi);
         sendBroadcast(intent);
@@ -310,81 +290,173 @@
         });
     }
 
+    private void sleep(int millis) {
+        try {
+            Thread.sleep(millis);
+        } catch (InterruptedException e) {
+            Log.e(TAG, "Error in thread sleep", e);
+        }
+    }
+
+    private void reliableWrite() {
+        mBluetoothGatt.beginReliableWrite();
+        sleep(1000);
+        writeCharacteristic(WRITE_VALUE);
+        sleep(1000);
+        if (!mBluetoothGatt.executeReliableWrite()) {
+            Log.w(TAG, "reliable write failed");
+        }
+        sleep(1000);
+        mBluetoothGatt.abortReliableWrite();
+    }
+
     private final BluetoothGattCallback mGattCallbacks = new BluetoothGattCallback() {
         @Override
         public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
             if (DEBUG) Log.d(TAG, "onConnectionStateChange");
             if (status == BluetoothGatt.GATT_SUCCESS) {
-                if (newState == BluetoothProfile.STATE_CONNECTED) notifyConnected();
-                else if (status == BluetoothProfile.STATE_DISCONNECTED) {
+                if (newState == BluetoothProfile.STATE_CONNECTED) {
+                    notifyConnected();
+                    stopScan();
+                    sleep(1000);
+                    mBluetoothGatt.discoverServices();
+                } else if (status == BluetoothProfile.STATE_DISCONNECTED) {
                     notifyDisconnected();
-                    showMessage("Bluetooth LE disconnected");
                 }
+            } else {
+                showMessage("Failed to connect");
             }
         }
 
         @Override
         public void onServicesDiscovered(BluetoothGatt gatt, int status) {
+            if (DEBUG) Log.d(TAG, "onServiceDiscovered");
             if ((status == BluetoothGatt.GATT_SUCCESS) &&
                 (mBluetoothGatt.getService(SERVICE_UUID) != null)) {
                 notifyServicesDiscovered();
-            }
-        }
-
-        @Override
-        public void onCharacteristicRead(BluetoothGatt gatt,
-                                         BluetoothGattCharacteristic characteristic, int status) {
-            if ((status == BluetoothGatt.GATT_SUCCESS) &&
-                (characteristic.getUuid().equals(CHARACTERISTIC_UUID))) {
-                notifyCharacteristicRead(characteristic.getStringValue(0));
+                sleep(1000);
+                writeCharacteristic(WRITE_VALUE);
             }
         }
 
         @Override
         public void onCharacteristicWrite(BluetoothGatt gatt,
                                           BluetoothGattCharacteristic characteristic, int status) {
-            if (DEBUG) Log.d(TAG, "onCharacteristicWrite: characteristic.val=" + characteristic.getStringValue(0)
-                                  + " status=" + status);
+            String value = characteristic.getStringValue(0);
+            if (DEBUG) Log.d(TAG, "onCharacteristicWrite: characteristic.val="
+                    + value + " status=" + status);
             BluetoothGattCharacteristic mCharacteristic = getCharacteristic(CHARACTERISTIC_UUID);
             if ((status == BluetoothGatt.GATT_SUCCESS) &&
-                (characteristic.getStringValue(0).equals(mCharacteristic.getStringValue(0)))) {
-                notifyCharacteristicWrite();
+                (value.equals(mCharacteristic.getStringValue(0)))) {
+                notifyCharacteristicWrite(value);
+                sleep(1000);
+                readCharacteristic();
+            } else {
+                notifyError("Failed to write characteristic: " + value);
             }
         }
 
         @Override
-        public void onCharacteristicChanged(BluetoothGatt gatt,
-                                            BluetoothGattCharacteristic characteristic) {
-            if (characteristic.getUuid().equals(UPDATE_CHARACTERISTIC_UUID))
-                notifyCharacteristicChanged(characteristic.getStringValue(0));
-        }
-
-        @Override
-        public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor,
-                                     int status) {
+        public void onCharacteristicRead(BluetoothGatt gatt,
+                                         BluetoothGattCharacteristic characteristic, int status) {
+            if (DEBUG) Log.d(TAG, "onCharacteristicRead");
             if ((status == BluetoothGatt.GATT_SUCCESS) &&
-                (descriptor.getUuid().equals(DESCRIPTOR_UUID))) {
-                notifyDescriptorRead(new String(descriptor.getValue()));
+                (characteristic.getUuid().equals(CHARACTERISTIC_UUID))) {
+                notifyCharacteristicRead(characteristic.getStringValue(0));
+                sleep(1000);
+                writeDescriptor(WRITE_VALUE);
+            } else {
+                notifyError("Failed to read characteristic");
             }
         }
 
         @Override
         public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor,
                                       int status) {
+            if (DEBUG) Log.d(TAG, "onDescriptorWrite");
             if ((status == BluetoothGatt.GATT_SUCCESS) &&
                 (descriptor.getUuid().equals(DESCRIPTOR_UUID))) {
-                notifyDescriptorWrite();
+                notifyDescriptorWrite(new String(descriptor.getValue()));
+                sleep(1000);
+                readDescriptor();
+            } else {
+                notifyError("Failed to write descriptor");
+            }
+        }
+
+        @Override
+        public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor,
+                                     int status) {
+            if (DEBUG) Log.d(TAG, "onDescriptorRead");
+            if ((status == BluetoothGatt.GATT_SUCCESS) &&
+                (descriptor.getUuid() != null) &&
+                (descriptor.getUuid().equals(DESCRIPTOR_UUID))) {
+                notifyDescriptorRead(new String(descriptor.getValue()));
+                sleep(1000);
+                setNotification(true);
+            } else {
+                notifyError("Failed to read descriptor");
+            }
+        }
+
+        @Override
+        public void onCharacteristicChanged(BluetoothGatt gatt,
+                                            BluetoothGattCharacteristic characteristic) {
+            if (DEBUG) Log.d(TAG, "onCharacteristicChanged");
+            if ((characteristic.getUuid() != null) &&
+                (characteristic.getUuid().equals(UPDATE_CHARACTERISTIC_UUID))) {
+                notifyCharacteristicChanged(characteristic.getStringValue(0));
+                setNotification(false);
+                sleep(1000);
+                mBluetoothGatt.readRemoteRssi();
             }
         }
 
         @Override
         public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
-            if (status == BluetoothGatt.GATT_SUCCESS) notifyReliableWriteCompleted();
+            if (DEBUG) Log.d(TAG, "onReliableWriteComplete: " + status);
+            if (status == BluetoothGatt.GATT_SUCCESS) {
+                notifyReliableWriteCompleted();
+            } else {
+                notifyError("Failed to complete reliable write: " + status);
+            }
+            sleep(1000);
+            mBluetoothGatt.disconnect();
         }
 
         @Override
         public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
-            if (status == BluetoothGatt.GATT_SUCCESS) notifyReadRemoteRssi(rssi);
+            if (DEBUG) Log.d(TAG, "onReadRemoteRssi");
+            if (status == BluetoothGatt.GATT_SUCCESS) {
+                notifyReadRemoteRssi(rssi);
+            } else {
+                notifyError("Failed to read remote rssi");
+            }
+            sleep(1000);
+            reliableWrite();
         }
     };
-}
\ No newline at end of file
+
+    private final ScanCallback mScanCallback = new ScanCallback() {
+        @Override
+        public void onScanResult(int callbackType, ScanResult result) {
+            if (mBluetoothGatt == null) {
+                mBluetoothGatt = result.getDevice().connectGatt(mContext, false, mGattCallbacks);
+            }
+        }
+    };
+
+    private void startScan() {
+        if (DEBUG) Log.d(TAG, "startScan");
+        List<ScanFilter> filter = Arrays.asList(new ScanFilter.Builder().setServiceUuid(
+                new ParcelUuid(BleServerService.ADV_SERVICE_UUID)).build());
+        ScanSettings setting = new ScanSettings.Builder()
+                .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
+        mScanner.startScan(filter, setting, mScanCallback);
+    }
+
+    private void stopScan() {
+        if (DEBUG) Log.d(TAG, "stopScan");
+        mScanner.stopScan(mScanCallback);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientStartActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientStartActivity.java
new file mode 100644
index 0000000..5a4e327
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientStartActivity.java
@@ -0,0 +1,147 @@
+/*
+ * 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 com.android.cts.verifier.bluetooth;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+public class BleClientStartActivity extends PassFailButtons.Activity {
+
+    private TestAdapter mTestAdapter;
+    private int mAllPassed;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.ble_server_start);
+        setPassFailButtonClickListeners();
+        setInfoResources(R.string.ble_server_start_name,
+                         R.string.ble_server_start_info, -1);
+        getPassButton().setEnabled(false);
+
+        mTestAdapter = new TestAdapter(this, setupTestList());
+        ListView listView = (ListView) findViewById(R.id.ble_server_tests);
+        listView.setAdapter(mTestAdapter);
+
+        mAllPassed = 0;
+        startService(new Intent(this, BleClientService.class));
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(BleClientService.BLE_BLUETOOTH_CONNECTED);
+        filter.addAction(BleClientService.BLE_BLUETOOTH_DISCONNECTED);
+        filter.addAction(BleClientService.BLE_SERVICES_DISCOVERED);
+        filter.addAction(BleClientService.BLE_CHARACTERISTIC_READ);
+        filter.addAction(BleClientService.BLE_CHARACTERISTIC_WRITE);
+        filter.addAction(BleClientService.BLE_CHARACTERISTIC_CHANGED);
+        filter.addAction(BleClientService.BLE_DESCRIPTOR_READ);
+        filter.addAction(BleClientService.BLE_DESCRIPTOR_WRITE);
+        filter.addAction(BleClientService.BLE_RELIABLE_WRITE_COMPLETED);
+        filter.addAction(BleClientService.BLE_READ_REMOTE_RSSI);
+        registerReceiver(onBroadcast, filter);
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        unregisterReceiver(onBroadcast);
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        stopService(new Intent(this, BleClientService.class));
+    }
+
+    private List<Integer> setupTestList() {
+        ArrayList<Integer> testList = new ArrayList<Integer>();
+        testList.add(R.string.ble_client_connect_name);
+        testList.add(R.string.ble_discover_service_name);
+        testList.add(R.string.ble_read_characteristic_name);
+        testList.add(R.string.ble_write_characteristic_name);
+        testList.add(R.string.ble_reliable_write_name);
+        testList.add(R.string.ble_notify_characteristic_name);
+        testList.add(R.string.ble_read_descriptor_name);
+        testList.add(R.string.ble_write_descriptor_name);
+        testList.add(R.string.ble_read_rssi_name);
+        testList.add(R.string.ble_client_disconnect_name);
+        return testList;
+    }
+
+    private BroadcastReceiver onBroadcast = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action == BleClientService.BLE_BLUETOOTH_CONNECTED) {
+                mTestAdapter.setTestPass(0);
+                mAllPassed |= 0x01;
+            } else if (action == BleClientService.BLE_SERVICES_DISCOVERED) {
+                mTestAdapter.setTestPass(1);
+                mAllPassed |= 0x02;
+            } else if (action == BleClientService.BLE_CHARACTERISTIC_READ) {
+                mTestAdapter.setTestPass(2);
+                mAllPassed |= 0x04;
+            } else if (action == BleClientService.BLE_CHARACTERISTIC_WRITE) {
+                mTestAdapter.setTestPass(3);
+                mAllPassed |= 0x08;
+            } else if (action == BleClientService.BLE_RELIABLE_WRITE_COMPLETED) {
+                mTestAdapter.setTestPass(4);
+                mAllPassed |= 0x10;
+            } else if (action == BleClientService.BLE_CHARACTERISTIC_CHANGED) {
+                mTestAdapter.setTestPass(5);
+                mAllPassed |= 0x20;
+            } else if (action == BleClientService.BLE_DESCRIPTOR_READ) {
+                mTestAdapter.setTestPass(6);
+                mAllPassed |= 0x40;
+            } else if (action == BleClientService.BLE_DESCRIPTOR_WRITE) {
+                mTestAdapter.setTestPass(7);
+                mAllPassed |= 0x80;
+            } else if (action == BleClientService.BLE_READ_REMOTE_RSSI) {
+                mTestAdapter.setTestPass(8);
+                mAllPassed |= 0x100;
+            } else if (action == BleClientService.BLE_BLUETOOTH_DISCONNECTED) {
+                mTestAdapter.setTestPass(9);
+                mAllPassed |= 0x200;
+            }
+            mTestAdapter.notifyDataSetChanged();
+            if (mAllPassed == 0x3FF) getPassButton().setEnabled(true);
+        }
+    };
+
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientTestActivity.java
deleted file mode 100644
index a13d934..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientTestActivity.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.verifier.bluetooth;
-
-import com.android.cts.verifier.ManifestTestListAdapter;
-import com.android.cts.verifier.PassFailButtons;
-import com.android.cts.verifier.R;
-
-import android.os.Bundle;
-
-public class BleClientTestActivity extends PassFailButtons.TestListActivity {
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.pass_fail_list);
-        setPassFailButtonClickListeners();
-        setInfoResources(R.string.ble_client_test_name, R.string.ble_client_test_info, -1);
-
-        setTestListAdapter(new ManifestTestListAdapter(this, getClass().getName()));
-    }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleDiscoverServiceActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleDiscoverServiceActivity.java
deleted file mode 100644
index 6896b04..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleDiscoverServiceActivity.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2013 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.verifier.bluetooth;
-
-public class BleDiscoverServiceActivity extends BleButtonActivity {
-    public BleDiscoverServiceActivity() {
-        super(BleButtonActivity.DISCOVER_SERVICE);
-    }
-}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleNotifyCharacteristicActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleNotifyCharacteristicActivity.java
deleted file mode 100644
index e0c79bf..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleNotifyCharacteristicActivity.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2013 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.verifier.bluetooth;
-
-import com.android.cts.verifier.PassFailButtons;
-import com.android.cts.verifier.R;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Bundle;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.Button;
-import android.widget.TextView;
-import android.widget.Toast;
-
-public class BleNotifyCharacteristicActivity extends PassFailButtons.Activity {
-
-    private boolean mEnable;
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.ble_notify_characteristic);
-        setPassFailButtonClickListeners();
-        setInfoResources(R.string.ble_notify_characteristic_name,
-                         R.string.ble_notify_characteristic_info, -1);
-
-        mEnable = false;
-
-        ((Button) findViewById(R.id.ble_notify)).setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                mEnable = !mEnable;
-                if (mEnable) ((Button) v).setText(getString(R.string.ble_stop_notification));
-                else ((Button) v).setText(getString(R.string.ble_begin_notification));
-
-                Intent intent = new Intent(BleNotifyCharacteristicActivity.this,
-                                           BleClientService.class);
-                intent.putExtra(BleClientService.EXTRA_COMMAND,
-                                BleClientService.COMMAND_SET_NOTIFICATION);
-                intent.putExtra(BleClientService.EXTRA_BOOL, mEnable);
-                startService(intent);
-            }
-        });
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(BleClientService.BLE_CHARACTERISTIC_CHANGED);
-        registerReceiver(onBroadcast, filter);
-    }
-
-    @Override
-    public void onPause() {
-        super.onPause();
-        unregisterReceiver(onBroadcast);
-        mEnable = false;
-        Intent intent = new Intent(BleNotifyCharacteristicActivity.this,
-                                   BleClientService.class);
-        intent.putExtra(BleClientService.EXTRA_COMMAND,
-                        BleClientService.COMMAND_SET_NOTIFICATION);
-        intent.putExtra(BleClientService.EXTRA_BOOL, mEnable);
-        startService(intent);
-    }
-
-    private BroadcastReceiver onBroadcast = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String value = intent.getStringExtra(BleClientService.EXTRA_CHARACTERISTIC_VALUE);
-            ((TextView) findViewById(R.id.ble_notify_text)).setText(value);
-        }
-    };
-}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleReadRssiActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleReadRssiActivity.java
deleted file mode 100644
index 800499c..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleReadRssiActivity.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2013 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.verifier.bluetooth;
-
-import com.android.cts.verifier.PassFailButtons;
-import com.android.cts.verifier.R;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Bundle;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.TextView;
-import android.widget.Toast;
-
-public class BleReadRssiActivity extends PassFailButtons.Activity {
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.ble_read_rssi);
-        setPassFailButtonClickListeners();
-        setInfoResources(R.string.ble_read_rssi_name,
-                         R.string.ble_read_rssi_info, -1);
-
-        ((Button) findViewById(R.id.ble_read_rssi)).setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                Intent intent = new Intent(BleReadRssiActivity.this, BleClientService.class);
-                intent.putExtra(BleClientService.EXTRA_COMMAND,
-                                BleClientService.COMMAND_READ_RSSI);
-                startService(intent);
-            }
-        });
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(BleClientService.BLE_READ_REMOTE_RSSI);
-        registerReceiver(onBroadcast, filter);
-    }
-
-    @Override
-    public void onPause() {
-        super.onPause();
-        unregisterReceiver(onBroadcast);
-    }
-
-    private BroadcastReceiver onBroadcast = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            int rssi = intent.getIntExtra(BleClientService.EXTRA_RSSI_VALUE, 128);
-            ((TextView) findViewById(R.id.ble_rssi_text)).setText(Integer.toString(rssi));
-        }
-    };
-}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleReadWriteActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleReadWriteActivity.java
deleted file mode 100644
index 22233ef..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleReadWriteActivity.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2013 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.verifier.bluetooth;
-
-import com.android.cts.verifier.PassFailButtons;
-import com.android.cts.verifier.R;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Bundle;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.TextView;
-import android.widget.Toast;
-
-class BleReadWriteActivity extends PassFailButtons.Activity {
-
-    static final int CHARACTERISTIC = 0;
-    static final int DESCRIPTOR = 1;
-
-    private int mWriteCommand;
-    private int mReadCommand;
-    private String mWriteFilter;
-    private String mReadFilter;
-    private String mExtraValue;
-    private int mName;
-    private EditText mEditText;
-
-    BleReadWriteActivity(int target) {
-        if (target == CHARACTERISTIC) {
-            mWriteCommand = BleClientService.COMMAND_WRITE_CHARACTERISTIC;
-            mReadCommand = BleClientService.COMMAND_READ_CHARACTERISTIC;
-            mWriteFilter = BleClientService.BLE_CHARACTERISTIC_WRITE;
-            mReadFilter = BleClientService.BLE_CHARACTERISTIC_READ;
-            mExtraValue = BleClientService.EXTRA_CHARACTERISTIC_VALUE;
-            mName = R.string.ble_client_characteristic_name;
-        } else if (target == DESCRIPTOR) {
-            mWriteCommand = BleClientService.COMMAND_WRITE_DESCRIPTOR;
-            mReadCommand = BleClientService.COMMAND_READ_DESCRIPTOR;
-            mWriteFilter = BleClientService.BLE_DESCRIPTOR_WRITE;
-            mReadFilter = BleClientService.BLE_DESCRIPTOR_READ;
-            mExtraValue = BleClientService.EXTRA_DESCRIPTOR_VALUE;
-            mName = R.string.ble_client_descriptor_name;
-        }
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.ble_client_read_write);
-        setPassFailButtonClickListeners();
-        setInfoResources(mName, R.string.ble_read_write_info, -1);
-
-        mEditText = (EditText) findViewById(R.id.write_text);
-
-        ((Button) findViewById(R.id.ble_write)).setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                String writeValue = mEditText.getText().toString();
-                Intent intent = new Intent(BleReadWriteActivity.this, BleClientService.class);
-                intent.putExtra(BleClientService.EXTRA_COMMAND, mWriteCommand);
-                intent.putExtra(BleClientService.EXTRA_WRITE_VALUE, writeValue);
-                startService(intent);
-                mEditText.setText("");
-            }
-        });
-
-        ((Button) findViewById(R.id.ble_read)).setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                Intent intent = new Intent(BleReadWriteActivity.this, BleClientService.class);
-                intent.putExtra(BleClientService.EXTRA_COMMAND, mReadCommand);
-                startService(intent);
-            }
-        });
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(mReadFilter);
-        filter.addAction(mWriteFilter);
-        registerReceiver(onBroadcast, filter);
-    }
-
-    @Override
-    public void onPause() {
-        super.onPause();
-        unregisterReceiver(onBroadcast);
-    }
-
-    private void showMessage(String msg) {
-        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
-    }
-
-    private BroadcastReceiver onBroadcast = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            if (action == mWriteFilter)
-                showMessage("Write successful callback");
-            else if (action == mReadFilter) {
-                String value = intent.getStringExtra(mExtraValue);
-                ((TextView) findViewById(R.id.read_text)).setText(value);
-            }
-        }
-    };
-}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleReliableWriteActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleReliableWriteActivity.java
deleted file mode 100644
index c7460b5..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleReliableWriteActivity.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2013 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.verifier.bluetooth;
-
-import com.android.cts.verifier.PassFailButtons;
-import com.android.cts.verifier.R;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Bundle;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.Toast;
-
-public class BleReliableWriteActivity extends PassFailButtons.Activity {
-
-    EditText mEditText;
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.ble_reliable_write);
-        setPassFailButtonClickListeners();
-        setInfoResources(R.string.ble_reliable_write_name, R.string.ble_reliable_write_info, -1);
-        getPassButton().setEnabled(false);
-        ((Button) findViewById(R.id.ble_execute)).setEnabled(false);
-
-        mEditText = (EditText) findViewById(R.id.write_text);
-
-        ((Button) findViewById(R.id.ble_begin)).setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                Intent intent = new Intent(BleReliableWriteActivity.this, BleClientService.class);
-                intent.putExtra(BleClientService.EXTRA_COMMAND,
-                                BleClientService.COMMAND_BEGIN_WRITE);
-                startService(intent);
-            }
-        });
-
-        ((Button) findViewById(R.id.ble_write)).setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                String writeValue = mEditText.getText().toString();
-                Intent intent = new Intent(BleReliableWriteActivity.this, BleClientService.class);
-                intent.putExtra(BleClientService.EXTRA_COMMAND,
-                                BleClientService.COMMAND_WRITE_CHARACTERISTIC);
-                intent.putExtra(BleClientService.EXTRA_WRITE_VALUE, writeValue);
-                startService(intent);
-                mEditText.setText("");
-            }
-        });
-
-        ((Button) findViewById(R.id.ble_execute)).setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                Intent intent = new Intent(BleReliableWriteActivity.this, BleClientService.class);
-                intent.putExtra(BleClientService.EXTRA_COMMAND,
-                                BleClientService.COMMAND_EXECUTE_WRITE);
-                startService(intent);
-            }
-        });
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(BleClientService.BLE_CHARACTERISTIC_WRITE);
-        filter.addAction(BleClientService.BLE_RELIABLE_WRITE_COMPLETED);
-        registerReceiver(onBroadcast, filter);
-    }
-
-    @Override
-    public void onPause() {
-        super.onPause();
-        unregisterReceiver(onBroadcast);
-        Intent intent = new Intent(this, BleClientService.class);
-        intent.putExtra(BleClientService.EXTRA_COMMAND, BleClientService.COMMAND_ABORT_RELIABLE);
-        startService(intent);
-    }
-
-    private void showMessage(String msg) {
-        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
-    }
-
-    private BroadcastReceiver onBroadcast = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            if (action == BleClientService.BLE_CHARACTERISTIC_WRITE) {
-                showMessage("Write value verified.");
-                ((Button) findViewById(R.id.ble_execute)).setEnabled(true);
-            } else if (action == BleClientService.BLE_RELIABLE_WRITE_COMPLETED) {
-                showMessage("Reliable write completed.");
-                getPassButton().setEnabled(true);
-            }
-        }
-    };
-}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleScannerPowerLevelActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleScannerPowerLevelActivity.java
index a6489c1..bf3484e 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleScannerPowerLevelActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleScannerPowerLevelActivity.java
@@ -59,6 +59,7 @@
         setPassFailButtonClickListeners();
         setInfoResources(R.string.ble_power_level_name,
                          R.string.ble_power_level_info, -1);
+        getPassButton().setEnabled(false);
 
         mTimerText = (TextView)findViewById(R.id.ble_timer);
         mTimer = new CountDownTimer(REFRESH_MAC_TIME, 1000) {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleScannerService.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleScannerService.java
index d3d96ac..eb71164 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleScannerService.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleScannerService.java
@@ -170,7 +170,7 @@
             if (serviceData.get(new ParcelUuid(BleAdvertiserService.POWER_LEVEL_UUID)) != null) {
                 byte[] data =
                         serviceData.get(new ParcelUuid(BleAdvertiserService.POWER_LEVEL_UUID));
-                if (data.length == 3) {
+                if (data.length == BleAdvertiserService.POWER_LEVEL_DATA.length) {
                     Intent powerIntent = new Intent(BLE_POWER_LEVEL);
                     powerIntent.putExtra(EXTRA_MAC_ADDRESS, result.getDevice().getAddress());
                     powerIntent.putExtra(EXTRA_POWER_LEVEL, record.getTxPowerLevel());
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleScannerTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleScannerTestActivity.java
index 1f54917..52933e0 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleScannerTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleScannerTestActivity.java
@@ -20,8 +20,12 @@
 import com.android.cts.verifier.PassFailButtons;
 import com.android.cts.verifier.R;
 
+import android.bluetooth.BluetoothAdapter;
 import android.os.Bundle;
 
+import java.util.ArrayList;
+import java.util.List;
+
 public class BleScannerTestActivity extends PassFailButtons.TestListActivity {
 
     @Override
@@ -31,6 +35,14 @@
         setPassFailButtonClickListeners();
         setInfoResources(R.string.ble_scanner_test_name,
                          R.string.ble_scanner_test_info, -1);
-        setTestListAdapter(new ManifestTestListAdapter(this, getClass().getName()));
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        List<String> disabledTest = new ArrayList<String>();
+        if (adapter == null || !adapter.isOffloadedFilteringSupported()) {
+            disabledTest.add(
+                    "com.android.cts.verifier.bluetooth.BleScannerHardwareScanFilterActivity");
+        }
+
+        setTestListAdapter(new ManifestTestListAdapter(this, getClass().getName(),
+                disabledTest.toArray(new String[disabledTest.size()])));
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleServerService.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleServerService.java
index 91b3a6c..8718f57 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleServerService.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleServerService.java
@@ -33,10 +33,15 @@
 import android.bluetooth.BluetoothGattService;
 import android.bluetooth.BluetoothManager;
 import android.bluetooth.BluetoothProfile;
+import android.bluetooth.le.AdvertiseCallback;
+import android.bluetooth.le.AdvertiseData;
+import android.bluetooth.le.AdvertiseSettings;
+import android.bluetooth.le.BluetoothLeAdvertiser;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.ParcelUuid;
 import android.util.Log;
 import android.widget.Toast;
 
@@ -76,6 +81,8 @@
             UUID.fromString("00009997-0000-1000-8000-00805f9b34fb");
     private static final UUID DESCRIPTOR_UUID =
             UUID.fromString("00009996-0000-1000-8000-00805f9b34fb");
+    public static final UUID ADV_SERVICE_UUID=
+            UUID.fromString("00003333-0000-1000-8000-00805f9b34fb");
 
     private BluetoothManager mBluetoothManager;
     private BluetoothGattServer mGattServer;
@@ -84,12 +91,14 @@
     private Timer mNotificationTimer;
     private Handler mHandler;
     private String mReliableWriteValue;
+    private BluetoothLeAdvertiser mAdvertiser;
 
     @Override
     public void onCreate() {
         super.onCreate();
 
         mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
+        mAdvertiser = mBluetoothManager.getAdapter().getBluetoothLeAdvertiser();
         mGattServer = mBluetoothManager.openGattServer(this, mCallbacks);
         mService = createService();
         if (mGattServer != null) {
@@ -106,6 +115,7 @@
 
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
+        startAdvertise();
         return START_NOT_STICKY;
     }
 
@@ -117,6 +127,7 @@
     @Override
     public void onDestroy() {
         super.onDestroy();
+        stopAdvertise();
         if (mGattServer == null) {
            return;
         }
@@ -366,5 +377,26 @@
             }
         }
     };
+
+    private void startAdvertise() {
+        if (DEBUG) Log.d(TAG, "startAdvertise");
+        AdvertiseData data = new AdvertiseData.Builder()
+            .addServiceData(new ParcelUuid(ADV_SERVICE_UUID), new byte[]{1,2,3})
+            .addServiceUuid(new ParcelUuid(ADV_SERVICE_UUID))
+            .build();
+        AdvertiseSettings setting = new AdvertiseSettings.Builder()
+            .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
+            .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM)
+            .setConnectable(true)
+            .build();
+        mAdvertiser.startAdvertising(setting, data, mAdvertiseCallback);
+    }
+
+    private void stopAdvertise() {
+        if (DEBUG) Log.d(TAG, "stopAdvertise");
+        mAdvertiser.stopAdvertising(mAdvertiseCallback);
+    }
+
+    private final AdvertiseCallback mAdvertiseCallback = new AdvertiseCallback(){};
 }
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BluetoothTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BluetoothTestActivity.java
index 9895f02..df70984 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BluetoothTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BluetoothTestActivity.java
@@ -20,7 +20,12 @@
 import com.android.cts.verifier.PassFailButtons;
 import com.android.cts.verifier.R;
 
+import android.bluetooth.BluetoothAdapter;
 import android.os.Bundle;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+import java.util.List;
 
 public class BluetoothTestActivity extends PassFailButtons.TestListActivity {
 
@@ -31,6 +36,34 @@
         setPassFailButtonClickListeners();
         setInfoResources(R.string.bluetooth_test, R.string.bluetooth_test_info, -1);
 
-        setTestListAdapter(new ManifestTestListAdapter(this, getClass().getName()));
+        BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+        if (bluetoothAdapter == null) {
+            Toast.makeText(this, "bluetooth not supported", Toast.LENGTH_SHORT);
+            return;
+        }
+
+        List<String> disabledTestArray = new ArrayList<String>();
+        for (String s : this.getResources().getStringArray(R.array.disabled_tests)) {
+            disabledTestArray.add(s);
+        }
+        if (!this.getPackageManager().hasSystemFeature("android.hardware.bluetooth_le")) {
+            disabledTestArray.add(
+                  "com.android.cts.verifier.bluetooth.BleAdvertiserTestActivity");
+            disabledTestArray.add(
+                  "com.android.cts.verifier.bluetooth.BleScannerTestActivity");
+            disabledTestArray.add(
+                  "com.android.cts.verifier.bluetooth.BleClientTestActivity");
+            disabledTestArray.add(
+                  "com.android.cts.verifier.bluetooth.BleServerStartActivity");
+        } else if (!bluetoothAdapter.isMultipleAdvertisementSupported()) {
+            disabledTestArray.add(
+                  "com.android.cts.verifier.bluetooth.BleAdvertiserTestActivity");
+            disabledTestArray.add(
+                  "com.android.cts.verifier.bluetooth.BleServerStartActivity");
+            disabledTestArray.add(
+                  "com.android.cts.verifier.bluetooth.BleScannerTestActivity");
+        }
+        setTestListAdapter(new ManifestTestListAdapter(this, getClass().getName(),
+                disabledTestArray.toArray(new String[disabledTestArray.size()])));
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/TestAdapter.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/TestAdapter.java
index 631fe36..8f4ed56 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/TestAdapter.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/TestAdapter.java
@@ -80,7 +80,7 @@
         if (convertView != null) {
             vg = (ViewGroup) convertView;
         } else {
-            vg = (ViewGroup) inflater.inflate(R.layout.ble_server_start_item, null);
+            vg = (ViewGroup) inflater.inflate(R.layout.ble_test_item, null);
         }
 
         Test test = tests.get(position);
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 e340c8a..e3d0b6d 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
@@ -112,6 +112,7 @@
     public static final String TRIGGER_AE_KEY = "ae";
     public static final String TRIGGER_AF_KEY = "af";
     public static final String VIB_PATTERN_KEY = "pattern";
+    public static final String EVCOMP_KEY = "evComp";
 
     private CameraManager mCameraManager = null;
     private HandlerThread mCameraThread = null;
@@ -543,6 +544,8 @@
                     doCapture(cmdObj);
                 } else if ("doVibrate".equals(cmdObj.getString("cmdName"))) {
                     doVibrate(cmdObj);
+                } else if ("getCameraIds".equals(cmdObj.getString("cmdName"))) {
+                    doGetCameraIds();
                 } else {
                     throw new ItsException("Unknown command: " + cmd);
                 }
@@ -728,6 +731,38 @@
         mSocketRunnableObj.sendResponse(mCameraCharacteristics);
     }
 
+    private void doGetCameraIds() throws ItsException {
+        String[] devices;
+        try {
+            devices = mCameraManager.getCameraIdList();
+            if (devices == null || devices.length == 0) {
+                throw new ItsException("No camera devices");
+            }
+        } catch (CameraAccessException e) {
+            throw new ItsException("Failed to get device ID list", e);
+        }
+
+        try {
+            JSONObject obj = new JSONObject();
+            JSONArray array = new JSONArray();
+            for (String id : devices) {
+                CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(id);
+                // Only supply camera Id for non-legacy cameras since legacy camera does not
+                // support ITS
+                if (characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) !=
+                        CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) {
+                    array.put(id);
+                }
+            }
+            obj.put("cameraIdArray", array);
+            mSocketRunnableObj.sendResponse("cameraIds", obj);
+        } catch (org.json.JSONException e) {
+            throw new ItsException("JSON error: ", e);
+        } catch (android.hardware.camera2.CameraAccessException e) {
+            throw new ItsException("Access error: ", e);
+        }
+    }
+
     private void prepareCaptureReader(int[] widths, int[] heights, int formats[], int numSurfaces) {
         if (mCaptureReaders != null) {
             for (int i = 0; i < mCaptureReaders.length; i++) {
@@ -802,6 +837,12 @@
             mNeedsLockedAE = params.optBoolean(LOCK_AE_KEY, false);
             mNeedsLockedAWB = params.optBoolean(LOCK_AWB_KEY, false);
 
+            // An EV compensation can be specified as part of AE convergence.
+            int evComp = params.optInt(EVCOMP_KEY, 0);
+            if (evComp != 0) {
+                Logt.i(TAG, String.format("Running 3A with AE exposure compensation value: %d", evComp));
+            }
+
             // By default, AE and AF both get triggered, but the user can optionally override this.
             // Also, AF won't get triggered if the lens is fixed-focus.
             boolean doAE = true;
@@ -815,8 +856,10 @@
                     doAF = triggers.getBoolean(TRIGGER_AF_KEY);
                 }
             }
-            if (doAF && mCameraCharacteristics.get(
-                            CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE) == 0) {
+            Float minFocusDistance = mCameraCharacteristics.get(
+                    CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE);
+            boolean isFixedFocusLens = minFocusDistance != null && minFocusDistance == 0.0;
+            if (doAF && isFixedFocusLens) {
                 // Send a dummy result back for the code that is waiting for this message to see
                 // that AF has converged.
                 Logt.i(TAG, "Ignoring request for AF on fixed-focus camera");
@@ -845,7 +888,11 @@
                 // at a time, to simplify the logic here.
                 if (!mInterlock3A.block(TIMEOUT_3A * 1000) ||
                         System.currentTimeMillis() - tstart > TIMEOUT_3A * 1000) {
-                    throw new ItsException("3A failed to converge (timeout)");
+                    throw new ItsException(
+                            "3A failed to converge after " + TIMEOUT_3A + " seconds.\n" +
+                            "AE converge state: " + mConvergedAE + ", \n" +
+                            "AF convergence state: " + mConvergedAF + ", \n" +
+                            "AWB convergence state: " + mConvergedAWB + ".");
                 }
                 mInterlock3A.close();
 
@@ -876,6 +923,10 @@
                     req.set(CaptureRequest.CONTROL_AWB_LOCK, false);
                     req.set(CaptureRequest.CONTROL_AWB_REGIONS, regionAWB);
 
+                    if (evComp != 0) {
+                        req.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, evComp);
+                    }
+
                     if (mConvergedAE && mNeedsLockedAE) {
                         req.set(CaptureRequest.CONTROL_AE_LOCK, true);
                     }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java
index 12b9bfc..1fdd044 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java
@@ -27,9 +27,19 @@
 import android.os.Bundle;
 import android.util.Log;
 import android.widget.Toast;
-import java.util.HashSet;
-import java.util.Arrays;
 
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.HashMap;
+import java.util.Arrays;
+import java.util.List;
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
 import com.android.cts.verifier.PassFailButtons;
 import com.android.cts.verifier.R;
 
@@ -41,7 +51,9 @@
  */
 public class ItsTestActivity extends PassFailButtons.Activity {
     private static final String TAG = "ItsTestActivity";
+    private static final String EXTRA_CAMERA_ID = "camera.its.extra.CAMERA_ID";
     private static final String EXTRA_SUCCESS = "camera.its.extra.SUCCESS";
+    private static final String EXTRA_SUMMARY = "camera.its.extra.SUMMARY";
     private static final String ACTION_ITS_RESULT =
             "com.android.cts.verifier.camera.its.ACTION_ITS_RESULT";
 
@@ -50,20 +62,32 @@
         public void onReceive(Context context, Intent intent) {
             Log.i(TAG, "Received result for Camera ITS tests");
             if (ACTION_ITS_RESULT.equals(intent.getAction())) {
+                String cameraId = intent.getStringExtra(EXTRA_CAMERA_ID);
                 String result = intent.getStringExtra(EXTRA_SUCCESS);
-                String[] parts = result.split("=");
-                if (parts.length != 2) {
-                    Toast.makeText(ItsTestActivity.this,
-                            "Received unknown ITS result string: " + result,
-                            Toast.LENGTH_SHORT).show();
+                String summaryPath = intent.getStringExtra(EXTRA_SUMMARY);
+                if (!mNonLegacyCameraIds.contains(cameraId)) {
+                    Log.e(TAG, "Unknown camera id " + cameraId + " reported to ITS");
+                    return;
                 }
-                String cameraId = parts[0];
-                boolean pass = parts[1].equals("True");
+
+                Log.i(TAG, "ITS summary path is: " + summaryPath);
+                mSummaryMap.put(cameraId, summaryPath);
+                // Create summary report
+                if (mSummaryMap.keySet().containsAll(mNonLegacyCameraIds)) {
+                    StringBuilder summary = new StringBuilder();
+                    for (String id : mNonLegacyCameraIds) {
+                        String path = mSummaryMap.get(id);
+                        appendFileContentToSummary(summary, path);
+                    }
+                    ItsTestActivity.this.getReportLog().setSummary(
+                            summary.toString(), 1.0, ResultType.NEUTRAL, ResultUnit.NONE);
+                }
+                boolean pass = result.equals("True");
                 if(pass) {
                     Log.i(TAG, "Received Camera " + cameraId + " ITS SUCCESS from host.");
                     mITSPassedCameraIds.add(cameraId);
-                    if (mCameraIds != null &&
-                            mITSPassedCameraIds.containsAll(Arrays.asList(mCameraIds))) {
+                    if (mNonLegacyCameraIds != null && mNonLegacyCameraIds.size() != 0 &&
+                            mITSPassedCameraIds.containsAll(mNonLegacyCameraIds)) {
                         ItsTestActivity.this.showToast(R.string.its_test_passed);
                         ItsTestActivity.this.getPassButton().setEnabled(true);
                     }
@@ -73,11 +97,40 @@
                 }
             }
         }
+
+        private void appendFileContentToSummary(StringBuilder summary, String path) {
+            BufferedReader reader = null;
+            try {
+                reader = new BufferedReader(new FileReader(path));
+                String line = null;
+                do {
+                    line = reader.readLine();
+                    if (line != null) {
+                        summary.append(line);
+                    }
+                } while (line != null);
+            } catch (FileNotFoundException e) {
+                Log.e(TAG, "Cannot find ITS summary file at " + path);
+                summary.append("Cannot find ITS summary file at " + path);
+            } catch (IOException e) {
+                Log.e(TAG, "IO exception when trying to read " + path);
+                summary.append("IO exception when trying to read " + path);
+            } finally {
+                if (reader != null) {
+                    try {
+                        reader.close();
+                    } catch (IOException e) {
+                    }
+                }
+            }
+        }
     }
 
     private final SuccessReceiver mSuccessReceiver = new SuccessReceiver();
     private final HashSet<String> mITSPassedCameraIds = new HashSet<>();
-    private String[] mCameraIds = null;
+    // map camera id to ITS summary report path
+    private final HashMap<String, String> mSummaryMap = new HashMap<>();
+    ArrayList<String> mNonLegacyCameraIds = null;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -85,6 +138,33 @@
         setContentView(R.layout.its_main);
         setInfoResources(R.string.camera_its_test, R.string.camera_its_test_info, -1);
         setPassFailButtonClickListeners();
+
+        // Hide the test if all camera devices are legacy
+        CameraManager manager = (CameraManager) this.getSystemService(Context.CAMERA_SERVICE);
+        try {
+            String[] cameraIds = manager.getCameraIdList();
+            mNonLegacyCameraIds = new ArrayList<String>();
+            boolean allCamerasAreLegacy = true;
+            for (String id : cameraIds) {
+                CameraCharacteristics characteristics = manager.getCameraCharacteristics(id);
+                if (characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)
+                        != CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) {
+                    mNonLegacyCameraIds.add(id);
+                    allCamerasAreLegacy = false;
+                }
+            }
+            if (allCamerasAreLegacy) {
+                showToast(R.string.all_legacy_devices);
+                ItsTestActivity.this.getReportLog().setSummary(
+                        "PASS: all cameras on this device are LEGACY"
+                        , 1.0, ResultType.NEUTRAL, ResultUnit.NONE);
+                setTestResultAndFinish(true);
+            }
+        } catch (CameraAccessException e) {
+            Toast.makeText(ItsTestActivity.this,
+                    "Received error from camera service while checking device capabilities: "
+                            + e, Toast.LENGTH_SHORT).show();
+        }
         getPassButton().setEnabled(false);
     }
 
@@ -95,26 +175,7 @@
         if (manager == null) {
             showToast(R.string.no_camera_manager);
         } else {
-            try {
-                mCameraIds = manager.getCameraIdList();
-                boolean allCamerasAreLegacy = true;
-                for (String id : mCameraIds) {
-                    CameraCharacteristics characteristics = manager.getCameraCharacteristics(id);
-                    if (characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)
-                            != CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) {
-                        allCamerasAreLegacy = false;
-                        break;
-                    }
-                }
-                if (allCamerasAreLegacy) {
-                    showToast(R.string.all_legacy_devices);
-                    getPassButton().setEnabled(false);
-                }
-            } catch (CameraAccessException e) {
-                Toast.makeText(ItsTestActivity.this,
-                        "Received error from camera service while checking device capabilities: "
-                                + e, Toast.LENGTH_SHORT).show();
-            }
+            Log.d(TAG, "register ITS result receiver");
             IntentFilter filter = new IntentFilter(ACTION_ITS_RESULT);
             registerReceiver(mSuccessReceiver, filter);
         }
@@ -123,6 +184,7 @@
     @Override
     protected void onPause() {
         super.onPause();
+        Log.d(TAG, "unregister ITS result receiver");
         unregisterReceiver(mSuccessReceiver);
     }
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/features/FeatureSummaryActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/features/FeatureSummaryActivity.java
index 74a5317..36acf42 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/features/FeatureSummaryActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/features/FeatureSummaryActivity.java
@@ -220,6 +220,9 @@
             new Feature(PackageManager.FEATURE_SENSOR_RELATIVE_HUMIDITY, false),
             new Feature(PackageManager.FEATURE_VERIFIED_BOOT, false),
 
+            // Features explicitly made optional in L
+            new Feature(PackageManager.FEATURE_LOCATION_NETWORK, false),
+
             // New hidden features in L
             new Feature("android.hardware.ethernet", false),
             new Feature("android.hardware.hdmi.cec", false),
@@ -238,6 +241,7 @@
         // features
         boolean hasWifi = false;
         boolean hasTelephony = false;
+        boolean hasBluetooth = false;
         boolean hasIllegalFeature = false;
 
         // get list of all features device thinks it has, & store in a HashMap
@@ -304,6 +308,7 @@
                 // device reports it -- yay! set the happy icon
                 hasWifi = hasWifi || PackageManager.FEATURE_WIFI.equals(f.name);
                 hasTelephony = hasTelephony || PackageManager.FEATURE_TELEPHONY.equals(f.name);
+                hasBluetooth = hasBluetooth || PackageManager.FEATURE_BLUETOOTH.equals(f.name);
                 statusIcon = R.drawable.fs_good;
                 actualFeatures.remove(f.name);
             } else if (!present && f.required) {
@@ -388,9 +393,11 @@
         if (hasIllegalFeature) {
             sb.append(getResources().getString(R.string.fs_disallowed)).append("\n");
         }
-        if (!hasWifi && !hasTelephony) {
+
+        if (!hasWifi && !hasTelephony && !hasBluetooth) {
             sb.append(getResources().getString(R.string.fs_missing_wifi_telephony)).append("\n");
         }
+
         String warnings = sb.toString().trim();
         if (warnings == null || "".equals(warnings)) {
             ((TextView) (findViewById(R.id.fs_warnings))).setVisibility(View.GONE);
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 6a9de44..c1cc1f9 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodFlowTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodFlowTestActivity.java
@@ -17,22 +17,22 @@
 package com.android.cts.verifier.managedprovisioning;
 
 import android.app.AlertDialog;
-import android.app.Dialog;
 import android.app.admin.DevicePolicyManager;
 import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.os.Bundle;
 import android.provider.Settings;
 import android.util.Log;
+import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.View.OnClickListener;
 import android.widget.ArrayAdapter;
+import android.widget.ImageView;
 import android.widget.ListView;
 import android.widget.TextView;
 import android.widget.Toast;
@@ -62,17 +62,26 @@
 
     private ComponentName mAdminReceiverComponent;
 
-    private TestAdapter mAdapter;
+    private TestAdapter mTestListAdapter;
     private View mStartProvisioningButton;
     private List<TestItem> mTests = new ArrayList<TestItem>();
 
     protected DevicePolicyManager mDevicePolicyManager;
 
     private TestItem mProfileOwnerInstalled;
-    private TestItem mProfileVisibleTest;
+    private TestItem mProfileAccountVisibleTest;
     private TestItem mDeviceAdminVisibleTest;
     private TestItem mWorkAppVisibleTest;
     private TestItem mCrossProfileIntentFiltersTest;
+    private TestItem mDisableNonMarketTest;
+    private TestItem mEnableNonMarketTest;
+    private TestItem mWorkNotificationBadgedTest;
+    private TestItem mAppSettingsVisibleTest;
+    private TestItem mLocationSettingsVisibleTest;
+    private TestItem mCredSettingsVisibleTest;
+    private TestItem mPrintSettingsVisibleTest;
+
+    private int mCurrentTestPosition;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -89,9 +98,11 @@
 
         setupTests();
 
-        mAdapter = new TestAdapter(this);
-        setListAdapter(mAdapter);
-        mAdapter.addAll(mTests);
+        mTestListAdapter = new TestAdapter(this);
+        setListAdapter(mTestListAdapter);
+        mTestListAdapter.addAll(mTests);
+
+        mCurrentTestPosition = 0;
 
         mStartProvisioningButton = findViewById(R.id.byod_start);
         mStartProvisioningButton.setOnClickListener(new OnClickListener() {
@@ -154,67 +165,147 @@
             }
         };
 
-        mProfileVisibleTest = new TestItem(this, R.string.provisioning_byod_profile_visible,
+        /*
+         * To keep the image in this test up to date, use the instructions in
+         * {@link ByodIconSamplerActivity}.
+         */
+        mWorkAppVisibleTest = new TestItemWithIcon(this,
+                R.string.provisioning_byod_workapps_visible,
+                R.string.provisioning_byod_workapps_visible_instruction,
+                new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME),
+                R.drawable.badged_icon);
+
+        mWorkNotificationBadgedTest = new TestItemWithIcon(this,
+                R.string.provisioning_byod_work_notification,
+                R.string.provisioning_byod_work_notification_instruction,
+                new Intent(WorkNotificationTestActivity.ACTION_WORK_NOTIFICATION),
+                R.drawable.ic_corp_icon);
+
+        mDisableNonMarketTest = new TestItem(this, R.string.provisioning_byod_nonmarket_deny,
+                R.string.provisioning_byod_nonmarket_deny_info,
+                new Intent(ByodHelperActivity.ACTION_INSTALL_APK)
+                        .putExtra(ByodHelperActivity.EXTRA_ALLOW_NON_MARKET_APPS, false));
+
+        mEnableNonMarketTest = new TestItem(this, R.string.provisioning_byod_nonmarket_allow,
+                R.string.provisioning_byod_nonmarket_allow_info,
+                new Intent(ByodHelperActivity.ACTION_INSTALL_APK)
+                        .putExtra(ByodHelperActivity.EXTRA_ALLOW_NON_MARKET_APPS, true));
+
+        mProfileAccountVisibleTest = new TestItem(this, R.string.provisioning_byod_profile_visible,
                 R.string.provisioning_byod_profile_visible_instruction,
                 new Intent(Settings.ACTION_SETTINGS));
 
+        mAppSettingsVisibleTest = new TestItem(this, R.string.provisioning_byod_app_settings,
+                R.string.provisioning_byod_app_settings_instruction,
+                new Intent(Settings.ACTION_APPLICATION_SETTINGS));
+
         mDeviceAdminVisibleTest = new TestItem(this, R.string.provisioning_byod_admin_visible,
                 R.string.provisioning_byod_admin_visible_instruction,
                 new Intent(Settings.ACTION_SECURITY_SETTINGS));
 
-        mWorkAppVisibleTest = new TestItem(this, R.string.provisioning_byod_workapps_visible,
-                R.string.provisioning_byod_workapps_visible_instruction,
-                new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME));
+        mCredSettingsVisibleTest = new TestItem(this, R.string.provisioning_byod_cred_settings,
+                R.string.provisioning_byod_cred_settings_instruction,
+                new Intent(Settings.ACTION_SECURITY_SETTINGS));
+
+        mLocationSettingsVisibleTest = new TestItem(this,
+                R.string.provisioning_byod_location_settings,
+                R.string.provisioning_byod_location_settings_instruction,
+                new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));
+
+        mPrintSettingsVisibleTest = new TestItem(this, R.string.provisioning_byod_print_settings,
+                R.string.provisioning_byod_print_settings_instruction,
+                new Intent(Settings.ACTION_PRINT_SETTINGS));
 
         Intent intent = new Intent(CrossProfileTestActivity.ACTION_CROSS_PROFILE);
-        Intent chooser = Intent.createChooser(intent, getResources().getString(R.string.provisioning_cross_profile_chooser));
+        Intent chooser = Intent.createChooser(intent,
+                getResources().getString(R.string.provisioning_cross_profile_chooser));
         mCrossProfileIntentFiltersTest = new TestItem(this,
                 R.string.provisioning_byod_cross_profile,
                 R.string.provisioning_byod_cross_profile_instruction,
                 chooser);
 
         mTests.add(mProfileOwnerInstalled);
-        mTests.add(mProfileVisibleTest);
-        mTests.add(mDeviceAdminVisibleTest);
+
+        // Badge related tests
         mTests.add(mWorkAppVisibleTest);
+        mTests.add(mWorkNotificationBadgedTest);
+
+        // Settings related tests.
+        mTests.add(mProfileAccountVisibleTest);
+        mTests.add(mDeviceAdminVisibleTest);
+        mTests.add(mCredSettingsVisibleTest);
+        mTests.add(mAppSettingsVisibleTest);
+        mTests.add(mLocationSettingsVisibleTest);
+        mTests.add(mPrintSettingsVisibleTest);
+
         mTests.add(mCrossProfileIntentFiltersTest);
+        mTests.add(mDisableNonMarketTest);
+        mTests.add(mEnableNonMarketTest);
     }
 
     @Override
     protected void onListItemClick(ListView l, View v, int position, long id) {
         super.onListItemClick(l, v, position, id);
+        mCurrentTestPosition = position;
         TestItem test = (TestItem) getListAdapter().getItem(position);
         test.performTest(this);
     }
 
     private void showManualTestDialog(final TestItem test) {
-        AlertDialog dialog = new AlertDialog.Builder(this)
+        AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this)
                 .setIcon(android.R.drawable.ic_dialog_info)
-                .setMessage(test.getManualTestInstruction())
+                .setTitle(R.string.provisioning_byod)
                 .setNeutralButton(R.string.provisioning_byod_go, null)
-                .setPositiveButton(R.string.pass_button_text, new DialogInterface.OnClickListener() {
+                .setPositiveButton(R.string.pass_button_text, new AlertDialog.OnClickListener() {
                     @Override
                     public void onClick(DialogInterface dialog, int which) {
+                        clearRemainingState(test);
                         setTestResult(test, TestResult.Passed);
                     }
                 })
-                .setNegativeButton(R.string.fail_button_text, new DialogInterface.OnClickListener() {
+                .setNegativeButton(R.string.fail_button_text, new AlertDialog.OnClickListener() {
                     @Override
                     public void onClick(DialogInterface dialog, int which) {
+                        clearRemainingState(test);
                         setTestResult(test, TestResult.Failed);
                     }
-                })
-                .create();
-        dialog.show();
-
+                });
+        View customView = test.getCustomView();
+        if (customView != null) {
+            dialogBuilder.setView(customView);
+        } else {
+            dialogBuilder.setMessage(test.getManualTestInstruction());
+        }
+        final AlertDialog dialog = dialogBuilder.show();
+        // Note: Setting the OnClickListener on the Dialog rather than the Builder, prevents the
+        // dialog being dismissed on onClick.
         dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(new OnClickListener() {
             @Override
             public void onClick(View v) {
-                ByodFlowTestActivity.this.startActivity(test.getManualTestIntent());
+                try {
+                    ByodFlowTestActivity.this.startActivity(test.getManualTestIntent());
+                } catch (ActivityNotFoundException e) {
+                    Toast.makeText(ByodFlowTestActivity.this,
+                            "Cannot start " + test.getManualTestIntent(), Toast.LENGTH_LONG).show();
+                    setTestResult(test, TestResult.Failed);
+                    dialog.dismiss();
+                }
             }
         });
     }
 
+    private void clearRemainingState(final TestItem test) {
+        if (WorkNotificationTestActivity.ACTION_WORK_NOTIFICATION.equals(
+                test.getManualTestIntent().getAction())) {
+            try {
+                ByodFlowTestActivity.this.startActivity(new Intent(
+                        WorkNotificationTestActivity.ACTION_CLEAR_WORK_NOTIFICATION));
+            } catch (ActivityNotFoundException e) {
+                // User shouldn't run this test before work profile is set up.
+            }
+        }
+    }
+
     private void setTestResult(TestItem test, TestResult result) {
         test.setPassFailState(result);
 
@@ -223,7 +314,9 @@
             testSucceeds &= (aTest.getPassFailState() == TestResult.Passed);
         }
         getPassButton().setEnabled(testSucceeds);
-        mAdapter.notifyDataSetChanged();
+        mTestListAdapter.notifyDataSetChanged();
+
+        this.getListView().smoothScrollToPosition(mCurrentTestPosition + 1);
     }
 
     private void startByodProvisioning() {
@@ -273,6 +366,10 @@
                 this, ByodHelperActivity.class),
                 PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
                 PackageManager.DONT_KILL_APP);
+        getPackageManager().setComponentEnabledSetting(new ComponentName(
+                this, WorkNotificationTestActivity.class),
+                PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+                PackageManager.DONT_KILL_APP);
     }
 
     private void showToast(int messageId) {
@@ -336,6 +433,33 @@
         public Intent getManualTestIntent() {
             return mManualIntent;
         }
+
+        public View getCustomView() {
+            return null;
+        }
+    }
+
+    static class TestItemWithIcon extends TestItem {
+
+        private int mImageResId;
+        private Context mContext;
+
+        public TestItemWithIcon(Context context, int nameResId, int testInstructionResId,
+                Intent testIntent, int imageResId) {
+            super(context, nameResId, testInstructionResId, testIntent);
+            mContext = context;
+            mImageResId = imageResId;
+        }
+
+        @Override
+        public View getCustomView() {
+            LayoutInflater layoutInflater = LayoutInflater.from(mContext);
+            View view = layoutInflater.inflate(R.layout.byod_custom_view,
+                    null /* root */);
+            ((ImageView) view.findViewById(R.id.sample_icon)).setImageResource(mImageResId);
+            ((TextView) view.findViewById(R.id.message)).setText(getManualTestInstruction());
+            return view;
+        }
     }
 
     static class TestAdapter extends ArrayAdapter<TestItem> {
@@ -366,5 +490,4 @@
             return view;
         }
     }
-
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodHelperActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodHelperActivity.java
index 5dac4bd..277324c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodHelperActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodHelperActivity.java
@@ -24,10 +24,14 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.net.Uri;
 import android.os.Bundle;
+import android.provider.Settings;
 import android.util.Log;
 import android.widget.Toast;
 
+import static android.provider.Settings.Secure.INSTALL_NON_MARKET_APPS;
+
 import com.android.cts.verifier.R;
 import com.android.cts.verifier.managedprovisioning.ByodFlowTestActivity.TestResult;
 
@@ -53,17 +57,33 @@
 
     public static final String EXTRA_PROVISIONED = "extra_provisioned";
 
-    private ComponentName mAdminReceiverComponent;
+    // Primary -> managed intent: set unknown sources restriction and install package
+    public static final String ACTION_INSTALL_APK = "com.android.cts.verifier.managedprovisioning.BYOD_INSTALL_APK";
+    public static final String EXTRA_ALLOW_NON_MARKET_APPS = INSTALL_NON_MARKET_APPS;
 
+    private static final int REQUEST_INSTALL_PACKAGE = 1;
+
+    private static final String ORIGINAL_SETTINGS_NAME = "original settings";
+    private Bundle mOriginalSettings;
+
+    private ComponentName mAdminReceiverComponent;
     private DevicePolicyManager mDevicePolicyManager;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        if (savedInstanceState != null) {
+            Log.w(TAG, "Restored state");
+            mOriginalSettings = savedInstanceState.getBundle(ORIGINAL_SETTINGS_NAME);
+        } else {
+            mOriginalSettings = new Bundle();
+        }
+
         mAdminReceiverComponent = new ComponentName(this, DeviceAdminTestReceiver.class.getName());
         mDevicePolicyManager = (DevicePolicyManager) getSystemService(
                 Context.DEVICE_POLICY_SERVICE);
-        String action = getIntent().getAction();
+        Intent intent = getIntent();
+        String action = intent.getAction();
         Log.d(TAG, "ByodHelperActivity.onCreate: " + action);
 
         // we are explicitly started by {@link DeviceAdminTestReceiver} after a successful provisioning.
@@ -83,16 +103,70 @@
                 mDevicePolicyManager.wipeData(0);
                 showToast(R.string.provisioning_byod_profile_deleted);
             }
+        } else if (action.equals(ACTION_INSTALL_APK)) {
+            boolean allowNonMarket = intent.getBooleanExtra(EXTRA_ALLOW_NON_MARKET_APPS, false);
+            boolean wasAllowed = getAllowNonMarket();
+
+            // Update permission to install non-market apps
+            setAllowNonMarket(allowNonMarket);
+            mOriginalSettings.putBoolean(INSTALL_NON_MARKET_APPS, wasAllowed);
+
+            // Request to install a non-market application- easiest way is to reinstall ourself
+            final Intent installIntent = new Intent(Intent.ACTION_INSTALL_PACKAGE)
+                    .setData(Uri.parse("package:" + getPackageName()))
+                    .putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
+                    .putExtra(Intent.EXTRA_RETURN_RESULT, true);
+            startActivityForResult(installIntent, REQUEST_INSTALL_PACKAGE);
+
+            // Not yet ready to finish- wait until the result comes back
+            return;
         }
         // This activity has no UI and is only used to respond to CtsVerifier in the primary side.
         finish();
     }
 
+    @Override
+    protected void onSaveInstanceState(final Bundle savedState) {
+        super.onSaveInstanceState(savedState);
+
+        savedState.putBundle(ORIGINAL_SETTINGS_NAME, mOriginalSettings);
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        switch (requestCode) {
+            case REQUEST_INSTALL_PACKAGE: {
+                Log.w(TAG, "Received REQUEST_INSTALL_PACKAGE, resultCode = " + resultCode);
+                if (mOriginalSettings.containsKey(INSTALL_NON_MARKET_APPS)) {
+                    // Restore original setting
+                    setAllowNonMarket(mOriginalSettings.getBoolean(INSTALL_NON_MARKET_APPS));
+                    mOriginalSettings.remove(INSTALL_NON_MARKET_APPS);
+                }
+                finish();
+                break;
+            }
+            default: {
+                Log.wtf(TAG, "Unknown requestCode " + requestCode + "; data = " + data);
+                break;
+            }
+        }
+    }
+
     private boolean isProfileOwner() {
         return mDevicePolicyManager.isAdminActive(mAdminReceiverComponent) &&
                 mDevicePolicyManager.isProfileOwnerApp(mAdminReceiverComponent.getPackageName());
     }
 
+    private boolean getAllowNonMarket() {
+        String value = Settings.Secure.getString(getContentResolver(), INSTALL_NON_MARKET_APPS);
+        return "1".equals(value);
+    }
+
+    private void setAllowNonMarket(boolean allow) {
+        mDevicePolicyManager.setSecureSetting(mAdminReceiverComponent, INSTALL_NON_MARKET_APPS,
+                (allow ? "1" : "0"));
+    }
+
     private void startActivityInPrimary(Intent intent) {
         // Disable app components in the current profile, so only the counterpart in the other
         // profile can respond (via cross-profile intent filter)
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodIconSamplerActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodIconSamplerActivity.java
new file mode 100644
index 0000000..c0579d0
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodIconSamplerActivity.java
@@ -0,0 +1,109 @@
+/*
+ * 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 com.android.cts.verifier.managedprovisioning;
+
+import android.app.Activity;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.UserHandle;
+import android.os.Process;
+import android.util.Log;
+
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+
+/**
+ * Activity used to generate sample image for {@link ByodFlowTestActivity} on a reference build.
+ *
+ * <p>Instructions: After Profile owner installed test has passed, run:
+ *  adb shell pm list users
+ *  adb shell am start -a com.android.cts.verifier.managedprovisioning.BYOD_SAMPLE_ICON \
+ *      --user <MANAGED_USER_ID>
+ * The icon can then be copied from /mnt/shell/emulated/<MANAGED_USER_ID>/badged_icon.png.
+ */
+public class ByodIconSamplerActivity  extends Activity {
+    static final String TAG = "ByodIconSamplerActivity";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        sampleImage();
+        // This activity has no UI
+        finish();
+    }
+    /**
+     * Writes a badged option of the CTS tests app icon on the sdcard.
+     * For test development only: this should be used to regenerate the asset every time we have
+     * a new badge.
+     */
+    private void sampleImage() {
+        UserHandle userHandle = Process.myUserHandle();
+        Log.d(TAG, "Sampling image for: " + userHandle);
+        Drawable drawable = getPackageManager().getUserBadgedIcon(getAppIcon(), userHandle);
+        Bitmap bitmap = convertToBitmap(drawable);
+        String fileName = Environment.getExternalStorageDirectory().getPath() + "/badged_icon.png";
+        FileOutputStream file = null;
+        try {
+            file = new FileOutputStream(fileName);
+            bitmap.compress(Bitmap.CompressFormat.PNG, 100, file);
+        } catch (FileNotFoundException e) {
+            Log.d(TAG, "sampleImage: FileNotFoundException ", e);
+        } finally {
+            try {
+                if (file != null) {
+                    file.close();
+                    Log.d(TAG, "Wrote badged icon to file: " + fileName);
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    private Drawable getAppIcon() {
+        try {
+            PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(),
+                    0 /* flags */);
+            if (packageInfo.applicationInfo != null) {
+                return getResources().getDrawable(packageInfo.applicationInfo.icon);
+            }
+        } catch (NameNotFoundException e) {
+            // Should not happen
+            Log.d(TAG, "getAppIcon: NameNotFoundException", e);
+        }
+        return null;
+    }
+
+    private static Bitmap convertToBitmap(Drawable icon) {
+        if (icon == null) {
+            return null;
+        }
+        Bitmap bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(), icon.getIntrinsicHeight(),
+                Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+        icon.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+        icon.draw(canvas);
+        return bitmap;
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceAdminTestReceiver.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceAdminTestReceiver.java
index 8dccac3..fa7bc4c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceAdminTestReceiver.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceAdminTestReceiver.java
@@ -26,8 +26,6 @@
 import android.util.Log;
 import android.widget.Toast;
 
-import com.android.cts.verifier.managedprovisioning.ByodHelperActivity;
-
 /**
  * Profile owner receiver for BYOD flow test.
  * Setup cross-profile intent filter after successful provisioning.
@@ -50,7 +48,10 @@
             IntentFilter filter = new IntentFilter();
             filter.addAction(ByodHelperActivity.ACTION_QUERY_PROFILE_OWNER);
             filter.addAction(ByodHelperActivity.ACTION_REMOVE_PROFILE_OWNER);
+            filter.addAction(ByodHelperActivity.ACTION_INSTALL_APK);
             filter.addAction(CrossProfileTestActivity.ACTION_CROSS_PROFILE);
+            filter.addAction(WorkNotificationTestActivity.ACTION_WORK_NOTIFICATION);
+            filter.addAction(WorkNotificationTestActivity.ACTION_CLEAR_WORK_NOTIFICATION);
             dpm.addCrossProfileIntentFilter(getWho(context), filter,
                     DevicePolicyManager.FLAG_MANAGED_CAN_ACCESS_PARENT);
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/WorkNotificationTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/WorkNotificationTestActivity.java
new file mode 100644
index 0000000..c85ccf5
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/WorkNotificationTestActivity.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2012 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.verifier.managedprovisioning;
+
+import android.app.Activity;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.os.Bundle;
+
+import com.android.cts.verifier.R;
+
+/**
+ * Test activity used to generate a notification.
+ */
+public class WorkNotificationTestActivity extends Activity {
+    public static final String ACTION_WORK_NOTIFICATION =
+            "com.android.cts.verifier.managedprovisioning.WORK_NOTIFICATION";
+    public static final String ACTION_CLEAR_WORK_NOTIFICATION =
+            "com.android.cts.verifier.managedprovisioning.CLEAR_WORK_NOTIFICATION";
+    private static final int NOTIFICATION_ID = 7;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        final String action = getIntent().getAction();
+        final NotificationManager notificationManager = (NotificationManager)
+                getSystemService(Context.NOTIFICATION_SERVICE);
+        if (ACTION_WORK_NOTIFICATION.equals(action)) {
+            final Notification notification = new Notification.Builder(this)
+                .setSmallIcon(R.drawable.icon)
+                .setContentTitle(getString(R.string.provisioning_byod_work_notification_title))
+                .setVisibility(Notification.VISIBILITY_PUBLIC)
+                .setAutoCancel(true)
+                .build();
+            notificationManager.notify(NOTIFICATION_ID, notification);
+        } else if (ACTION_CLEAR_WORK_NOTIFICATION.equals(action)) {
+            notificationManager.cancel(NOTIFICATION_ID);
+        }
+        finish();
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/BaseEmulatorActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/BaseEmulatorActivity.java
index 8aa82b5..5f3a10a 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/BaseEmulatorActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/BaseEmulatorActivity.java
@@ -45,7 +45,8 @@
             PrefixPaymentService2.COMPONENT,
             PrefixTransportService1.COMPONENT,
             PrefixTransportService2.COMPONENT,
-            PrefixAccessService.COMPONENT)
+            PrefixAccessService.COMPONENT,
+            LargeNumAidsService.COMPONENT)
     );
 
     @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceEmulatorTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceEmulatorTestActivity.java
index 87fa3d1..b30bb73 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceEmulatorTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceEmulatorTestActivity.java
@@ -104,6 +104,10 @@
                     new Intent(this, OnAndOffHostEmulatorActivity.class), null));
 
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+                adapter.add(TestListItem.newTest(this, R.string.nfc_hce_large_num_aids_emulator,
+                        LargeNumAidsEmulatorActivity.class.getName(),
+                        new Intent(this, LargeNumAidsEmulatorActivity.class), null));
+
                 adapter.add(TestListItem.newTest(this, R.string.nfc_hce_payment_dynamic_aids_emulator,
                         DynamicAidEmulatorActivity.class.getName(),
                         new Intent(this, DynamicAidEmulatorActivity.class), null));
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceReaderTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceReaderTestActivity.java
index f628fb7..035ce86 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceReaderTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceReaderTestActivity.java
@@ -16,6 +16,8 @@
 
 package com.android.cts.verifier.nfc.hce;
 
+import android.nfc.NfcAdapter;
+import android.nfc.cardemulation.CardEmulation;
 import com.android.cts.verifier.ArrayTestListAdapter;
 import com.android.cts.verifier.PassFailButtons;
 import com.android.cts.verifier.R;
@@ -101,21 +103,29 @@
                     SimpleReaderActivity.class.getName(),
                     DynamicAidEmulatorActivity.buildReaderIntent(this), null));
 
-            adapter.add(TestListItem.newTest(this, R.string.nfc_hce_payment_prefix_aids_reader,
+            adapter.add(TestListItem.newTest(this, R.string.nfc_hce_large_num_aids_reader,
                     SimpleReaderActivity.class.getName(),
-                    PrefixPaymentEmulatorActivity.buildReaderIntent(this), null));
+                    LargeNumAidsEmulatorActivity.buildReaderIntent(this), null));
 
-            adapter.add(TestListItem.newTest(this, R.string.nfc_hce_payment_prefix_aids_reader_2,
-                    SimpleReaderActivity.class.getName(),
-                    PrefixPaymentEmulator2Activity.buildReaderIntent(this), null));
+            NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
+            CardEmulation cardEmulation = CardEmulation.getInstance(nfcAdapter);
+            if (cardEmulation.supportsAidPrefixRegistration()) {
+                adapter.add(TestListItem.newTest(this, R.string.nfc_hce_payment_prefix_aids_reader,
+                        SimpleReaderActivity.class.getName(),
+                        PrefixPaymentEmulatorActivity.buildReaderIntent(this), null));
 
-            adapter.add(TestListItem.newTest(this, R.string.nfc_hce_other_prefix_aids_reader,
-                    SimpleReaderActivity.class.getName(),
-                    DualNonPaymentPrefixEmulatorActivity.buildReaderIntent(this), null));
+                adapter.add(TestListItem.newTest(this, R.string.nfc_hce_payment_prefix_aids_reader_2,
+                        SimpleReaderActivity.class.getName(),
+                        PrefixPaymentEmulator2Activity.buildReaderIntent(this), null));
 
-            adapter.add(TestListItem.newTest(this, R.string.nfc_hce_other_conflicting_prefix_aids_reader,
-                    SimpleReaderActivity.class.getName(),
-                    ConflictingNonPaymentPrefixEmulatorActivity.buildReaderIntent(this), null));
+                adapter.add(TestListItem.newTest(this, R.string.nfc_hce_other_prefix_aids_reader,
+                        SimpleReaderActivity.class.getName(),
+                        DualNonPaymentPrefixEmulatorActivity.buildReaderIntent(this), null));
+
+                adapter.add(TestListItem.newTest(this, R.string.nfc_hce_other_conflicting_prefix_aids_reader,
+                        SimpleReaderActivity.class.getName(),
+                        ConflictingNonPaymentPrefixEmulatorActivity.buildReaderIntent(this), null));
+            }
         }
 
         setTestListAdapter(adapter);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceUtils.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceUtils.java
index c67169a..698948b 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceUtils.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceUtils.java
@@ -22,6 +22,9 @@
     public static final String TRANSPORT_PREFIX_AID = "F001020304";
     public static final String ACCESS_PREFIX_AID = "F005060708";
 
+    public static final String LARGE_NUM_AIDS_PREFIX = "F00102030414";
+    public static final String LARGE_NUM_AIDS_POSTFIX ="81";
+
     public static void enableComponent(PackageManager pm, ComponentName component) {
         pm.setComponentEnabledSetting(
                 component,
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/LargeNumAidsEmulatorActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/LargeNumAidsEmulatorActivity.java
new file mode 100644
index 0000000..db58252
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/LargeNumAidsEmulatorActivity.java
@@ -0,0 +1,59 @@
+package com.android.cts.verifier.nfc.hce;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.nfc.cardemulation.CardEmulation;
+import android.os.Bundle;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.nfc.NfcDialogs;
+
+import java.util.ArrayList;
+
+public class LargeNumAidsEmulatorActivity extends BaseEmulatorActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+       super.onCreate(savedInstanceState);
+       setContentView(R.layout.pass_fail_text);
+       setPassFailButtonClickListeners();
+       getPassButton().setEnabled(false);
+       setupServices(this, LargeNumAidsService.COMPONENT);
+    }
+
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+    }
+
+
+    @Override
+    void onServicesSetup(boolean result) {
+        ArrayList<String> aids = new ArrayList<String>();
+        for (int i = 0; i < 256; i++) {
+            aids.add(HceUtils.LARGE_NUM_AIDS_PREFIX + String.format("%02X", i) + HceUtils.LARGE_NUM_AIDS_POSTFIX);
+        }
+        mCardEmulation.registerAidsForService(LargeNumAidsService.COMPONENT,
+                CardEmulation.CATEGORY_OTHER, aids);
+    }
+
+    @Override
+    void onApduSequenceComplete(ComponentName component, long duration) {
+        if (component.equals(LargeNumAidsService.COMPONENT)) {
+            getPassButton().setEnabled(true);
+        }
+    }
+
+    public static Intent buildReaderIntent(Context context) {
+        Intent readerIntent = new Intent(context, SimpleReaderActivity.class);
+        readerIntent.putExtra(SimpleReaderActivity.EXTRA_APDUS,
+                LargeNumAidsService.getCommandSequence());
+        readerIntent.putExtra(SimpleReaderActivity.EXTRA_RESPONSES,
+                LargeNumAidsService.getResponseSequence());
+        readerIntent.putExtra(SimpleReaderActivity.EXTRA_LABEL,
+                context.getString(R.string.nfc_hce_large_num_aids_reader));
+        return readerIntent;
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/LargeNumAidsService.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/LargeNumAidsService.java
new file mode 100644
index 0000000..5883543
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/LargeNumAidsService.java
@@ -0,0 +1,37 @@
+package com.android.cts.verifier.nfc.hce;
+
+import android.content.ComponentName;
+
+public class LargeNumAidsService extends HceService {
+    static final String TAG = "LargeNumAidsService";
+
+    static final ComponentName COMPONENT =
+            new ComponentName("com.android.cts.verifier",
+            LargeNumAidsService.class.getName());
+
+    public static final CommandApdu[] getCommandSequence() {
+        CommandApdu[] commands = new CommandApdu[256];
+        for (int i = 0; i < 256; i++) {
+            commands[i] = HceUtils.buildSelectApdu(HceUtils.LARGE_NUM_AIDS_PREFIX + String.format("%02X", i) +
+                    HceUtils.LARGE_NUM_AIDS_POSTFIX, true);
+        }
+        return commands;
+    }
+
+    public static final String[] getResponseSequence() {
+        String[] responses = new String[256];
+        for (int i = 0; i < 256; i++) {
+            responses[i] = "9000" + String.format("%02X", i);
+        }
+        return responses;
+    }
+
+    public LargeNumAidsService() {
+        initialize(getCommandSequence(), getResponseSequence());
+    }
+
+    @Override
+    public ComponentName getComponent() {
+        return COMPONENT;
+    }
+}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/AttentionManagementVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/AttentionManagementVerifierActivity.java
new file mode 100644
index 0000000..d8f196a
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/AttentionManagementVerifierActivity.java
@@ -0,0 +1,931 @@
+/*
+ * 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 com.android.cts.verifier.notifications;
+
+import static com.android.cts.verifier.notifications.MockListener.JSON_AMBIENT;
+import static com.android.cts.verifier.notifications.MockListener.JSON_MATCHES_ZEN_FILTER;
+import static com.android.cts.verifier.notifications.MockListener.JSON_TAG;
+
+import android.app.Notification;
+import android.content.ContentProviderOperation;
+import android.content.OperationApplicationException;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.service.notification.NotificationListenerService;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import com.android.cts.verifier.R;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class AttentionManagementVerifierActivity
+        extends InteractiveVerifierActivity {
+    private static final String TAG = "NoListenerAttentionVerifier";
+
+    private static final String ALICE = "Alice";
+    private static final String ALICE_PHONE = "+16175551212";
+    private static final String ALICE_EMAIL = "alice@_foo._bar";
+    private static final String BOB = "Bob";
+    private static final String BOB_PHONE = "+16505551212";;
+    private static final String BOB_EMAIL = "bob@_foo._bar";
+    private static final String CHARLIE = "Charlie";
+    private static final String CHARLIE_PHONE = "+13305551212";
+    private static final String CHARLIE_EMAIL = "charlie@_foo._bar";
+    private static final int MODE_NONE = 0;
+    private static final int MODE_URI = 1;
+    private static final int MODE_PHONE = 2;
+    private static final int MODE_EMAIL = 3;
+
+    private Uri mAliceUri;
+    private Uri mBobUri;
+    private Uri mCharlieUri;
+
+    @Override
+    int getTitleResource() {
+        return R.string.attention_test;
+    }
+
+    @Override
+    int getInstructionsResource() {
+        return R.string.attention_info;
+    }
+
+    // Test Setup
+
+    @Override
+    protected List<InteractiveTestCase> createTestItems() {
+        List<InteractiveTestCase> tests = new ArrayList<>(17);
+        tests.add(new IsEnabledTest());
+        tests.add(new ServiceStartedTest());
+        tests.add(new InsertContactsTest());
+        tests.add(new SetModeNoneTest());
+        tests.add(new NoneInterceptsAllTest());
+        tests.add(new SetModePriorityTest());
+        tests.add(new PriorityInterceptsSomeTest());
+        tests.add(new SetModeAllTest());
+        tests.add(new AllInterceptsNothingTest());
+        tests.add(new DefaultOrderTest());
+        tests.add(new PrioritytOrderTest());
+        tests.add(new InterruptionOrderTest());
+        tests.add(new AmbientBitsTest());
+        tests.add(new LookupUriOrderTest());
+        tests.add(new EmailOrderTest());
+        tests.add(new PhoneOrderTest());
+        tests.add(new DeleteContactsTest());
+        return tests;
+    }
+
+    // Tests
+
+    protected class InsertContactsTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.attention_create_contacts);
+        }
+
+        @Override
+        void setUp() {
+            insertSingleContact(ALICE, ALICE_PHONE, ALICE_EMAIL, true);
+            insertSingleContact(BOB, BOB_PHONE, BOB_EMAIL, false);
+            // charlie is not in contacts
+            status = READY;
+            // wait for insertions to move through the system
+            delay();
+        }
+
+        @Override
+        void test() {
+            mAliceUri = lookupContact(ALICE_PHONE);
+            mBobUri = lookupContact(BOB_PHONE);
+            mCharlieUri = lookupContact(CHARLIE_PHONE);
+
+            status = PASS;
+            if (mAliceUri == null) { status = FAIL; }
+            if (mBobUri == null) { status = FAIL; }
+            if (mCharlieUri != null) { status = FAIL; }
+            next();
+        }
+    }
+
+    protected class DeleteContactsTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.attention_delete_contacts);
+        }
+
+        @Override
+        void test() {
+            final ArrayList<ContentProviderOperation> operationList = new ArrayList<>();
+            operationList.add(ContentProviderOperation.newDelete(mAliceUri).build());
+            operationList.add(ContentProviderOperation.newDelete(mBobUri).build());
+            try {
+                mContext.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operationList);
+                status = READY;
+            } catch (RemoteException e) {
+                Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
+                status = FAIL;
+            } catch (OperationApplicationException e) {
+                Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
+                status = FAIL;
+            }
+            status = PASS;
+            next();
+        }
+    }
+
+    protected class SetModeNoneTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createRetryItem(parent, R.string.attention_filter_none);
+        }
+
+        @Override
+        void test() {
+            MockListener.probeFilter(mContext,
+                    new MockListener.IntegerResultCatcher() {
+                        @Override
+                        public void accept(int mode) {
+                            if (mode == NotificationListenerService.INTERRUPTION_FILTER_NONE) {
+                                status = PASS;
+                                next();
+                            } else {
+                                Log.i("SetModeNoneTest", "waiting, current mode is: " + mode);
+                                status = WAIT_FOR_USER;
+                            }
+                        }
+                    });
+        }
+
+        @Override
+        void tearDown() {
+            mNm.cancelAll();
+            MockListener.resetListenerData(mContext);
+            delay();
+        }
+    }
+
+    protected class NoneInterceptsAllTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.attention_all_are_filtered);
+        }
+
+        @Override
+        void setUp() {
+            sendNotifications(MODE_URI, false, false);
+            status = READY;
+            // wait for notifications to move through the system
+            delay();
+        }
+
+        @Override
+        void test() {
+            MockListener.probeListenerPayloads(mContext,
+                    new MockListener.StringListResultCatcher() {
+                        @Override
+                        public void accept(List<String> result) {
+                            Set<String> found = new HashSet<String>();
+                            if (result == null || result.size() == 0) {
+                                status = FAIL;
+                                next();
+                                return;
+                            }
+                            boolean pass = true;
+                            for (String payloadData : result) {
+                                try {
+                                    JSONObject payload = new JSONObject(payloadData);
+                                    String tag = payload.getString(JSON_TAG);
+                                    boolean zen = payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
+                                    Log.e(TAG, tag + (zen ? "" : " not") + " intercepted");
+                                    if (found.contains(tag)) {
+                                        // multiple entries for same notification!
+                                        pass = false;
+                                    } else if (ALICE.equals(tag)) {
+                                        found.add(ALICE);
+                                        pass &= !zen;
+                                    } else if (BOB.equals(tag)) {
+                                        found.add(BOB);
+                                        pass &= !zen;
+                                    } else if (CHARLIE.equals(tag)) {
+                                        found.add(CHARLIE);
+                                        pass &= !zen;
+                                    }
+                                } catch (JSONException e) {
+                                    pass = false;
+                                    Log.e(TAG, "failed to unpack data from mocklistener", e);
+                                }
+                            }
+                            pass &= found.size() == 3;
+                            status = pass ? PASS : FAIL;
+                            next();
+                        }
+                    });
+            delay();  // in case the catcher never returns
+        }
+
+        @Override
+        void tearDown() {
+            mNm.cancelAll();
+            MockListener.resetListenerData(mContext);
+            delay();
+        }
+
+    }
+
+    protected class SetModeAllTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createRetryItem(parent, R.string.attention_filter_all);
+        }
+
+        @Override
+        void test() {
+            MockListener.probeFilter(mContext,
+                    new MockListener.IntegerResultCatcher() {
+                        @Override
+                        public void accept(int mode) {
+                            if (mode == NotificationListenerService.INTERRUPTION_FILTER_ALL) {
+                                status = PASS;
+                                next();
+                            } else {
+                                Log.i("SetModeAllTest", "waiting, current mode is: " + mode);
+                                status = WAIT_FOR_USER;
+                            }
+                        }
+                    });
+        }
+    }
+
+    protected class AllInterceptsNothingTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.attention_none_are_filtered);
+        }
+
+        @Override
+        void setUp() {
+            sendNotifications(MODE_URI, false, false);
+            status = READY;
+            // wait for notifications to move through the system
+            delay();
+        }
+
+        @Override
+        void test() {
+            MockListener.probeListenerPayloads(mContext,
+                    new MockListener.StringListResultCatcher() {
+                        @Override
+                        public void accept(List<String> result) {
+                            Set<String> found = new HashSet<String>();
+                            if (result == null || result.size() == 0) {
+                                status = FAIL;
+                                return;
+                            }
+                            boolean pass = true;
+                            for (String payloadData : result) {
+                                try {
+                                    JSONObject payload = new JSONObject(payloadData);
+                                    String tag = payload.getString(JSON_TAG);
+                                    boolean zen = payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
+                                    Log.e(TAG, tag + (zen ? "" : " not") + " intercepted");
+                                    if (found.contains(tag)) {
+                                        // multiple entries for same notification!
+                                        pass = false;
+                                    } else if (ALICE.equals(tag)) {
+                                        found.add(ALICE);
+                                        pass &= zen;
+                                    } else if (BOB.equals(tag)) {
+                                        found.add(BOB);
+                                        pass &= zen;
+                                    } else if (CHARLIE.equals(tag)) {
+                                        found.add(CHARLIE);
+                                        pass &= zen;
+                                    }
+                                } catch (JSONException e) {
+                                    pass = false;
+                                    Log.e(TAG, "failed to unpack data from mocklistener", e);
+                                }
+                            }
+                            pass &= found.size() == 3;
+                            status = pass ? PASS : FAIL;
+                            next();
+                        }
+                    });
+            delay();  // in case the catcher never returns
+        }
+
+        @Override
+        void tearDown() {
+            mNm.cancelAll();
+            MockListener.resetListenerData(mContext);
+            delay();
+        }
+    }
+
+    protected class SetModePriorityTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createRetryItem(parent, R.string.attention_filter_priority);
+        }
+
+        @Override
+        void test() {
+            MockListener.probeFilter(mContext,
+                    new MockListener.IntegerResultCatcher() {
+                        @Override
+                        public void accept(int mode) {
+                            if (mode == NotificationListenerService.INTERRUPTION_FILTER_PRIORITY) {
+                                status = PASS;
+                                next();
+                            } else {
+                                Log.i("SetModePriorityTest", "waiting, current mode is: " + mode);
+                                status = WAIT_FOR_USER;
+                            }
+                        }
+                    });
+        }
+    }
+
+    protected class PriorityInterceptsSomeTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.attention_some_are_filtered);
+        }
+
+        @Override
+        void setUp() {
+            sendNotifications(MODE_URI, false, false);
+            status = READY;
+            // wait for notifications to move through the system
+            delay();
+        }
+
+        @Override
+        void test() {
+            MockListener.probeListenerPayloads(mContext,
+                    new MockListener.StringListResultCatcher() {
+                        @Override
+                        public void accept(List<String> result) {
+                            Set<String> found = new HashSet<String>();
+                            if (result == null || result.size() == 0) {
+                                status = FAIL;
+                                return;
+                            }
+                            boolean pass = true;
+                            for (String payloadData : result) {
+                                try {
+                                    JSONObject payload = new JSONObject(payloadData);
+                                    String tag = payload.getString(JSON_TAG);
+                                    boolean zen = payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
+                                    Log.e(TAG, tag + (zen ? "" : " not") + " intercepted");
+                                    if (found.contains(tag)) {
+                                        // multiple entries for same notification!
+                                        pass = false;
+                                    } else if (ALICE.equals(tag)) {
+                                        found.add(ALICE);
+                                        pass &= zen;
+                                    } else if (BOB.equals(tag)) {
+                                        found.add(BOB);
+                                        pass &= !zen;
+                                    } else if (CHARLIE.equals(tag)) {
+                                        found.add(CHARLIE);
+                                        pass &= !zen;
+                                    }
+                                } catch (JSONException e) {
+                                    pass = false;
+                                    Log.e(TAG, "failed to unpack data from mocklistener", e);
+                                }
+                            }
+                            pass &= found.size() == 3;
+                            status = pass ? PASS : FAIL;
+                            next();
+                        }
+                    });
+            delay();  // in case the catcher never returns
+        }
+
+        @Override
+        void tearDown() {
+            mNm.cancelAll();
+            MockListener.resetListenerData(mContext);
+            delay();
+        }
+    }
+
+    // ordered by time: C, B, A
+    protected class DefaultOrderTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.attention_default_order);
+        }
+
+        @Override
+        void setUp() {
+            sendNotifications(MODE_NONE, false, false);
+            status = READY;
+            // wait for notifications to move through the system
+            delay();
+        }
+
+        @Override
+        void test() {
+            MockListener.probeListenerOrder(mContext,
+                    new MockListener.StringListResultCatcher() {
+                        @Override
+                        public void accept(List<String> orderedKeys) {
+                            int rankA = findTagInKeys(ALICE, orderedKeys);
+                            int rankB = findTagInKeys(BOB, orderedKeys);
+                            int rankC = findTagInKeys(CHARLIE, orderedKeys);
+                            if (rankC < rankB && rankB < rankA) {
+                                status = PASS;
+                            } else {
+                                logFail(rankA + ", " + rankB + ", " + rankC);
+                                status = FAIL;
+                            }
+                            next();
+                        }
+                    });
+            delay();  // in case the catcher never returns
+        }
+
+        @Override
+        void tearDown() {
+            mNm.cancelAll();
+            MockListener.resetListenerData(mContext);
+            delay();
+        }
+    }
+
+    // ordered by priority: B, C, A
+    protected class PrioritytOrderTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.attention_priority_order);
+        }
+
+        @Override
+        void setUp() {
+            sendNotifications(MODE_NONE, true, false);
+            status = READY;
+            // wait for notifications to move through the system
+            delay();
+        }
+
+        @Override
+        void test() {
+            MockListener.probeListenerOrder(mContext,
+                    new MockListener.StringListResultCatcher() {
+                        @Override
+                        public void accept(List<String> orderedKeys) {
+                            int rankA = findTagInKeys(ALICE, orderedKeys);
+                            int rankB = findTagInKeys(BOB, orderedKeys);
+                            int rankC = findTagInKeys(CHARLIE, orderedKeys);
+                            if (rankB < rankC && rankC < rankA) {
+                                status = PASS;
+                            } else {
+                                logFail(rankA + ", " + rankB + ", " + rankC);
+                                status = FAIL;
+                            }
+                            next();
+                        }
+                    });
+            delay();  // in case the catcher never returns
+        }
+
+        @Override
+        void tearDown() {
+            mNm.cancelAll();
+            MockListener.resetListenerData(mContext);
+            delay();
+        }
+    }
+
+    // A starts at the top then falls to the bottom
+    protected class InterruptionOrderTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.attention_interruption_order);
+        }
+
+        @Override
+        void setUp() {
+            sendNotifications(MODE_NONE, false, true);
+            status = READY;
+            // wait for notifications to move through the system
+            delay();
+        }
+
+        @Override
+        void test() {
+            if (status == READY) {
+                MockListener.probeListenerOrder(mContext,
+                        new MockListener.StringListResultCatcher() {
+                            @Override
+                            public void accept(List<String> orderedKeys) {
+                                int rankA = findTagInKeys(ALICE, orderedKeys);
+                                int rankB = findTagInKeys(BOB, orderedKeys);
+                                int rankC = findTagInKeys(CHARLIE, orderedKeys);
+                                if (rankA < rankB && rankA < rankC) {
+                                    status = RETEST;
+                                    delay(12000);
+                                } else {
+                                    logFail("noisy notification did not sort to top.");
+                                    status = FAIL;
+                                    next();
+                                }
+                            }
+                        });
+                delay();  // in case the catcher never returns
+            } else {
+                MockListener.probeListenerOrder(mContext,
+                        new MockListener.StringListResultCatcher() {
+                            @Override
+                            public void accept(List<String> orderedKeys) {
+                                int rankA = findTagInKeys(ALICE, orderedKeys);
+                                int rankB = findTagInKeys(BOB, orderedKeys);
+                                int rankC = findTagInKeys(CHARLIE, orderedKeys);
+                                if (rankA > rankB && rankA > rankC) {
+                                    status = PASS;
+                                } else {
+                                    logFail("noisy notification did not fade back into the list.");
+                                    status = FAIL;
+                                }
+                                next();
+                            }
+                        });
+                delay();  // in case the catcher never returns
+            }
+        }
+
+        @Override
+        void tearDown() {
+            mNm.cancelAll();
+            MockListener.resetListenerData(mContext);
+            delay();
+        }
+    }
+
+    // B & C above the fold, A below
+    protected class AmbientBitsTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.attention_ambient_bit);
+        }
+
+        @Override
+        void setUp() {
+            sendNotifications(MODE_NONE, true, false);
+            status = READY;
+            // wait for notifications to move through the system
+            delay();
+        }
+
+        @Override
+        void test() {
+            MockListener.probeListenerPayloads(mContext,
+                    new MockListener.StringListResultCatcher() {
+                        @Override
+                        public void accept(List<String> result) {
+                            Set<String> found = new HashSet<String>();
+                            if (result == null || result.size() == 0) {
+                                status = FAIL;
+                                return;
+                            }
+                            boolean pass = true;
+                            for (String payloadData : result) {
+                                try {
+                                    JSONObject payload = new JSONObject(payloadData);
+                                    String tag = payload.getString(JSON_TAG);
+                                    boolean ambient = payload.getBoolean(JSON_AMBIENT);
+                                    Log.e(TAG, tag + (ambient ? " is" : " isn't") + " ambient");
+                                    if (found.contains(tag)) {
+                                        // multiple entries for same notification!
+                                        pass = false;
+                                    } else if (ALICE.equals(tag)) {
+                                        found.add(ALICE);
+                                        pass &= ambient;
+                                    } else if (BOB.equals(tag)) {
+                                        found.add(BOB);
+                                        pass &= !ambient;
+                                    } else if (CHARLIE.equals(tag)) {
+                                        found.add(CHARLIE);
+                                        pass &= !ambient;
+                                    }
+                                } catch (JSONException e) {
+                                    pass = false;
+                                    Log.e(TAG, "failed to unpack data from mocklistener", e);
+                                }
+                            }
+                            pass &= found.size() == 3;
+                            status = pass ? PASS : FAIL;
+                            next();
+                        }
+                    });
+            delay();  // in case the catcher never returns
+        }
+
+        @Override
+        void tearDown() {
+            mNm.cancelAll();
+            MockListener.resetListenerData(mContext);
+            delay();
+        }
+    }
+
+    // ordered by contact affinity: A, B, C
+    protected class LookupUriOrderTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.attention_lookup_order);
+        }
+
+        @Override
+        void setUp() {
+            sendNotifications(MODE_URI, false, false);
+            status = READY;
+            // wait for notifications to move through the system
+            delay();
+        }
+
+        @Override
+        void test() {
+            MockListener.probeListenerOrder(mContext,
+                    new MockListener.StringListResultCatcher() {
+                        @Override
+                        public void accept(List<String> orderedKeys) {
+                            int rankA = findTagInKeys(ALICE, orderedKeys);
+                            int rankB = findTagInKeys(BOB, orderedKeys);
+                            int rankC = findTagInKeys(CHARLIE, orderedKeys);
+                            if (rankA < rankB && rankB < rankC) {
+                                status = PASS;
+                            } else {
+                                logFail(rankA + ", " + rankB + ", " + rankC);
+                                status = FAIL;
+                            }
+                            next();
+                        }
+                    });
+            delay();  // in case the catcher never returns
+        }
+
+        @Override
+        void tearDown() {
+            mNm.cancelAll();
+            MockListener.resetListenerData(mContext);
+            delay();
+        }
+    }
+
+    // ordered by contact affinity: A, B, C
+    protected class EmailOrderTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.attention_email_order);
+        }
+
+        @Override
+        void setUp() {
+            sendNotifications(MODE_EMAIL, false, false);
+            status = READY;
+            // wait for notifications to move through the system
+            delay();
+        }
+
+        @Override
+        void test() {
+            MockListener.probeListenerOrder(mContext,
+                    new MockListener.StringListResultCatcher() {
+                        @Override
+                        public void accept(List<String> orderedKeys) {
+                            int rankA = findTagInKeys(ALICE, orderedKeys);
+                            int rankB = findTagInKeys(BOB, orderedKeys);
+                            int rankC = findTagInKeys(CHARLIE, orderedKeys);
+                            if (rankA < rankB && rankB < rankC) {
+                                status = PASS;
+                            } else {
+                                logFail(rankA + ", " + rankB + ", " + rankC);
+                                status = FAIL;
+                            }
+                            next();
+                        }
+                    });
+            delay();  // in case the catcher never returns
+        }
+
+        @Override
+        void tearDown() {
+            mNm.cancelAll();
+            MockListener.resetListenerData(mContext);
+            delay();
+        }
+    }
+
+    // ordered by contact affinity: A, B, C
+    protected class PhoneOrderTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.attention_phone_order);
+        }
+
+        @Override
+        void setUp() {
+            sendNotifications(MODE_PHONE, false, false);
+            status = READY;
+            // wait for notifications to move through the system
+            delay();
+        }
+
+        @Override
+        void test() {
+            MockListener.probeListenerOrder(mContext,
+                    new MockListener.StringListResultCatcher() {
+                        @Override
+                        public void accept(List<String> orderedKeys) {
+                            int rankA = findTagInKeys(ALICE, orderedKeys);
+                            int rankB = findTagInKeys(BOB, orderedKeys);
+                            int rankC = findTagInKeys(CHARLIE, orderedKeys);
+                            if (rankA < rankB && rankB < rankC) {
+                                status = PASS;
+                            } else {
+                                logFail(rankA + ", " + rankB + ", " + rankC);
+                                status = FAIL;
+                            }
+                            next();
+                        }
+                    });
+            delay();  // in case the catcher never returns
+        }
+
+        @Override
+        void tearDown() {
+            mNm.cancelAll();
+            MockListener.resetListenerData(mContext);
+            delay();
+        }
+    }
+
+    // Utilities
+
+    // usePriorities true: B, C, A
+    // usePriorities false:
+    //   MODE_NONE: C, B, A
+    //   otherwise: A, B ,C
+    private void sendNotifications(int annotationMode, boolean usePriorities, boolean noisy) {
+        // TODO(cwren) Fixes flakey tests due to bug 17644321. Remove this line when it is fixed.
+        int baseId = NOTIFICATION_ID + (noisy ? 3 : 0);
+
+        // C, B, A when sorted by time.  Times must be in the past.
+        long whenA = System.currentTimeMillis() - 4000000L;
+        long whenB = System.currentTimeMillis() - 2000000L;
+        long whenC = System.currentTimeMillis() - 1000000L;
+
+        // B, C, A when sorted by priorities
+        int priorityA = usePriorities ? Notification.PRIORITY_MIN : Notification.PRIORITY_DEFAULT;
+        int priorityB = usePriorities ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT;
+        int priorityC = usePriorities ? Notification.PRIORITY_LOW : Notification.PRIORITY_DEFAULT;
+
+        Notification.Builder alice = new Notification.Builder(mContext)
+                .setContentTitle(ALICE)
+                .setContentText(ALICE)
+                .setSmallIcon(R.drawable.ic_stat_alice)
+                .setPriority(priorityA)
+                .setCategory(Notification.CATEGORY_MESSAGE)
+                .setWhen(whenA);
+        alice.setDefaults(noisy ? Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE : 0);
+        addPerson(annotationMode, alice, mAliceUri, ALICE_PHONE, ALICE_EMAIL);
+        mNm.notify(ALICE, baseId + 1, alice.build());
+
+        Notification.Builder bob = new Notification.Builder(mContext)
+                .setContentTitle(BOB)
+                .setContentText(BOB)
+                .setSmallIcon(R.drawable.ic_stat_bob)
+                .setPriority(priorityB)
+                .setCategory(Notification.CATEGORY_MESSAGE)
+                .setWhen(whenB);
+        addPerson(annotationMode, bob, mBobUri, BOB_PHONE, BOB_EMAIL);
+        mNm.notify(BOB, baseId + 2, bob.build());
+
+        Notification.Builder charlie = new Notification.Builder(mContext)
+                .setContentTitle(CHARLIE)
+                .setContentText(CHARLIE)
+                .setSmallIcon(R.drawable.ic_stat_charlie)
+                .setPriority(priorityC)
+                .setCategory(Notification.CATEGORY_MESSAGE)
+                .setWhen(whenC);
+        addPerson(annotationMode, charlie, mCharlieUri, CHARLIE_PHONE, CHARLIE_EMAIL);
+        mNm.notify(CHARLIE, baseId + 3, charlie.build());
+    }
+
+    private void addPerson(int mode, Notification.Builder note,
+            Uri uri, String phone, String email) {
+        if (mode == MODE_URI && uri != null) {
+            note.addPerson(uri.toString());
+        } else if (mode == MODE_PHONE) {
+            note.addPerson(Uri.fromParts("tel", phone, null).toString());
+        } else if (mode == MODE_EMAIL) {
+            note.addPerson(Uri.fromParts("mailto", email, null).toString());
+        }
+    }
+
+    private void insertSingleContact(String name, String phone, String email, boolean starred) {
+        final ArrayList<ContentProviderOperation> operationList =
+                new ArrayList<ContentProviderOperation>();
+        ContentProviderOperation.Builder builder =
+                ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI);
+        builder.withValue(ContactsContract.RawContacts.STARRED, starred ? 1 : 0);
+        operationList.add(builder.build());
+
+        builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI);
+        builder.withValueBackReference(StructuredName.RAW_CONTACT_ID, 0);
+        builder.withValue(ContactsContract.Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
+        builder.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name);
+        operationList.add(builder.build());
+
+        if (phone != null) {
+            builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI);
+            builder.withValueBackReference(Phone.RAW_CONTACT_ID, 0);
+            builder.withValue(ContactsContract.Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
+            builder.withValue(Phone.TYPE, Phone.TYPE_MOBILE);
+            builder.withValue(Phone.NUMBER, phone);
+            builder.withValue(ContactsContract.Data.IS_PRIMARY, 1);
+            operationList.add(builder.build());
+        }
+        if (email != null) {
+            builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI);
+            builder.withValueBackReference(Email.RAW_CONTACT_ID, 0);
+            builder.withValue(ContactsContract.Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
+            builder.withValue(Email.TYPE, Email.TYPE_HOME);
+            builder.withValue(Email.DATA, email);
+            operationList.add(builder.build());
+        }
+
+        try {
+            mContext.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operationList);
+        } catch (RemoteException e) {
+            Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
+        } catch (OperationApplicationException e) {
+            Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
+        }
+    }
+
+    private Uri lookupContact(String phone) {
+        Cursor c = null;
+        try {
+            Uri phoneUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
+                    Uri.encode(phone));
+            String[] projection = new String[] { ContactsContract.Contacts._ID,
+                    ContactsContract.Contacts.LOOKUP_KEY };
+            c = mContext.getContentResolver().query(phoneUri, projection, null, null, null);
+            if (c != null && c.getCount() > 0) {
+                c.moveToFirst();
+                int lookupIdx = c.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY);
+                int idIdx = c.getColumnIndex(ContactsContract.Contacts._ID);
+                String lookupKey = c.getString(lookupIdx);
+                long contactId = c.getLong(idIdx);
+                return ContactsContract.Contacts.getLookupUri(contactId, lookupKey);
+            }
+        } catch (Throwable t) {
+            Log.w(TAG, "Problem getting content resolver or performing contacts query.", t);
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+        return null;
+    }
+
+    /** Search a list of notification keys for a givcen tag. */
+    private int findTagInKeys(String tag, List<String> orderedKeys) {
+        for (int i = 0; i < orderedKeys.size(); i++) {
+            if (orderedKeys.get(i).contains(tag)) {
+                return i;
+            }
+        }
+        return -1;
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java
new file mode 100644
index 0000000..b658e4f
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java
@@ -0,0 +1,438 @@
+/*
+ * Copyright (C) 2013 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.verifier.notifications;
+
+import android.app.Activity;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.provider.Settings.Secure;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.LinkedBlockingQueue;
+
+public abstract class InteractiveVerifierActivity extends PassFailButtons.Activity
+        implements Runnable {
+    private static final String TAG = "InteractiveVerifier";
+    private static final String STATE = "state";
+    private static final String STATUS = "status";
+    private static LinkedBlockingQueue<String> sDeletedQueue = new LinkedBlockingQueue<String>();
+    protected static final String LISTENER_PATH = "com.android.cts.verifier/" +
+            "com.android.cts.verifier.notifications.MockListener";
+    protected static final int SETUP = 0;
+    protected static final int READY = 1;
+    protected static final int RETEST = 2;
+    protected static final int PASS = 3;
+    protected static final int FAIL = 4;
+    protected static final int WAIT_FOR_USER = 5;
+
+    protected static final int NOTIFICATION_ID = 1001;
+
+    // TODO remove these once b/10023397 is fixed
+    public static final String ENABLED_NOTIFICATION_LISTENERS = "enabled_notification_listeners";
+    public static final String NOTIFICATION_LISTENER_SETTINGS =
+            "android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS";
+
+    protected InteractiveTestCase mCurrentTest;
+    protected PackageManager mPackageManager;
+    protected NotificationManager mNm;
+    protected Context mContext;
+    protected Runnable mRunner;
+    protected View mHandler;
+    protected String mPackageString;
+
+    private LayoutInflater mInflater;
+    private ViewGroup mItemList;
+    private List<InteractiveTestCase> mTestList;
+    private Iterator<InteractiveTestCase> mTestOrder;
+
+    public static class DismissService extends Service {
+        @Override
+        public IBinder onBind(Intent intent) {
+            return null;
+        }
+
+        @Override
+        public void onStart(Intent intent, int startId) {
+            if(intent != null) { sDeletedQueue.offer(intent.getAction()); }
+        }
+    }
+
+    protected abstract class InteractiveTestCase {
+        int status;
+        private View view;
+
+        abstract View inflate(ViewGroup parent);
+        View getView(ViewGroup parent) {
+            if (view == null) {
+                view = inflate(parent);
+            }
+            return view;
+        }
+
+        /** @return true if the test should re-run when the test activity starts. */
+        boolean autoStart() {
+            return false;
+        }
+
+        /** Set status to {@link #READY} to proceed, or {@link #SETUP} to try again. */
+        void setUp() { status = READY; next(); };
+
+        /** Set status to {@link #PASS} or @{link #FAIL} to proceed, or {@link #READY} to retry. */
+        void test() { status = FAIL; next(); };
+
+        /** Do not modify status. */
+        void tearDown() { next(); };
+
+        protected void logFail() {
+            logFail(null);
+        }
+
+        protected void logFail(String message) {
+            logWithStack("failed " + this.getClass().getSimpleName() +
+                    ((message == null) ? "" : ": " + message));
+        }
+    }
+
+    abstract int getTitleResource();
+    abstract int getInstructionsResource();
+
+    protected void onCreate(Bundle savedState) {
+        super.onCreate(savedState);
+        int savedStateIndex = (savedState == null) ? 0 : savedState.getInt(STATE, 0);
+        int savedStatus = (savedState == null) ? SETUP : savedState.getInt(STATUS, SETUP);
+        Log.i(TAG, "restored state(" + savedStateIndex + "}, status(" + savedStatus + ")");
+        mContext = this;
+        mRunner = this;
+        mNm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+        mPackageManager = getPackageManager();
+        mInflater = getLayoutInflater();
+        View view = mInflater.inflate(R.layout.nls_main, null);
+        mItemList = (ViewGroup) view.findViewById(R.id.nls_test_items);
+        mHandler = mItemList;
+        mTestList = new ArrayList<>();
+        mTestList.addAll(createTestItems());
+        for (InteractiveTestCase test: mTestList) {
+            mItemList.addView(test.getView(mItemList));
+        }
+        mTestOrder = mTestList.iterator();
+        for (int i = 0; i < savedStateIndex; i++) {
+            mCurrentTest = mTestOrder.next();
+            mCurrentTest.status = PASS;
+        }
+        mCurrentTest = mTestOrder.next();
+        mCurrentTest.status = savedStatus;
+
+        setContentView(view);
+        setPassFailButtonClickListeners();
+        getPassButton().setEnabled(false);
+
+        setInfoResources(getTitleResource(), getInstructionsResource(), -1);
+    }
+
+    @Override
+    protected void onSaveInstanceState (Bundle outState) {
+        final int stateIndex = mTestList.indexOf(mCurrentTest);
+        outState.putInt(STATE, stateIndex);
+        outState.putInt(STATUS, mCurrentTest.status);
+        Log.i(TAG, "saved state(" + stateIndex + "}, status(" + (mCurrentTest.status) + ")");
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        if (mCurrentTest.autoStart()) {
+            mCurrentTest.status = READY;
+        }
+        next();
+    }
+
+    // Interface Utilities
+
+    protected void markItem(InteractiveTestCase test) {
+        if (test == null) { return; }
+        View item = test.view;
+        ImageView status = (ImageView) item.findViewById(R.id.nls_status);
+        View button = item.findViewById(R.id.nls_action_button);
+        switch (test.status) {
+            case WAIT_FOR_USER:
+                status.setImageResource(R.drawable.fs_warning);
+                break;
+
+            case SETUP:
+            case READY:
+            case RETEST:
+                status.setImageResource(R.drawable.fs_clock);
+                break;
+
+            case FAIL:
+                status.setImageResource(R.drawable.fs_error);
+                button.setClickable(false);
+                button.setEnabled(false);
+                break;
+
+            case PASS:
+                status.setImageResource(R.drawable.fs_good);
+                button.setClickable(false);
+                button.setEnabled(false);
+                break;
+
+        }
+        status.invalidate();
+    }
+
+    protected View createNlsSettingsItem(ViewGroup parent, int messageId) {
+        return createUserItem(parent, R.string.nls_start_settings, messageId);
+    }
+
+    protected View createRetryItem(ViewGroup parent, int messageId, Object... messageFormatArgs) {
+        return createUserItem(parent, R.string.attention_ready, messageId, messageFormatArgs);
+    }
+
+    protected View createUserItem(ViewGroup parent, int actionId, int messageId,
+            Object... messageFormatArgs) {
+        View item = mInflater.inflate(R.layout.nls_item, parent, false);
+        TextView instructions = (TextView) item.findViewById(R.id.nls_instructions);
+        instructions.setText(getString(messageId, messageFormatArgs));
+        Button button = (Button) item.findViewById(R.id.nls_action_button);
+        button.setText(actionId);
+        button.setTag(actionId);
+        return item;
+    }
+
+    protected View  createAutoItem(ViewGroup parent, int stringId) {
+        View item = mInflater.inflate(R.layout.nls_item, parent, false);
+        TextView instructions = (TextView) item.findViewById(R.id.nls_instructions);
+        instructions.setText(stringId);
+        View button = item.findViewById(R.id.nls_action_button);
+        button.setVisibility(View.GONE);
+        return item;
+    }
+
+    // Test management
+
+    abstract protected List<InteractiveTestCase> createTestItems();
+
+    public void run() {
+        if (mCurrentTest == null) { return; }
+        markItem(mCurrentTest);
+        switch (mCurrentTest.status) {
+            case SETUP:
+                Log.i(TAG, "running setup for: " + mCurrentTest.getClass().getSimpleName());
+                mCurrentTest.setUp();
+                break;
+
+            case WAIT_FOR_USER:
+                Log.i(TAG, "waiting for user: " + mCurrentTest.getClass().getSimpleName());
+                break;
+
+            case READY:
+            case RETEST:
+                Log.i(TAG, "running test for: " + mCurrentTest.getClass().getSimpleName());
+                mCurrentTest.test();
+                break;
+
+            case FAIL:
+                Log.i(TAG, "FAIL: " + mCurrentTest.getClass().getSimpleName());
+                mCurrentTest = null;
+                break;
+
+            case PASS:
+                Log.i(TAG, "pass for: " + mCurrentTest.getClass().getSimpleName());
+                mCurrentTest.tearDown();
+                if (mTestOrder.hasNext()) {
+                    mCurrentTest = mTestOrder.next();
+                    Log.i(TAG, "next test is: " + mCurrentTest.getClass().getSimpleName());
+                } else {
+                    Log.i(TAG, "no more tests");
+                    mCurrentTest = null;
+                    getPassButton().setEnabled(true);
+                    mNm.cancelAll();
+                }
+                break;
+        }
+        markItem(mCurrentTest);
+    }
+
+    /**
+     * Return to the state machine to progress through the tests.
+     */
+    protected void next() {
+        mHandler.removeCallbacks(mRunner);
+        mHandler.post(mRunner);
+    }
+
+    /**
+     * Wait for things to settle before returning to the state machine.
+     */
+    protected void delay() {
+        delay(3000);
+    }
+
+    /**
+     * Wait for some time.
+     */
+    protected void delay(long waitTime) {
+        mHandler.removeCallbacks(mRunner);
+        mHandler.postDelayed(mRunner, waitTime);
+    }
+
+    // UI callbacks
+
+    public void launchSettings() {
+        startActivity(new Intent(NOTIFICATION_LISTENER_SETTINGS));
+    }
+
+    public void actionPressed(View v) {
+        Object tag = v.getTag();
+        if (tag instanceof Integer) {
+            int id = ((Integer) tag).intValue();
+            if (id == R.string.nls_start_settings) {
+                launchSettings();
+            } else if (id == R.string.attention_ready) {
+                mCurrentTest.status = READY;
+                next();
+            }
+        }
+    }
+
+    // Utilities
+
+    protected PendingIntent makeIntent(int code, String tag) {
+        Intent intent = new Intent(tag);
+        intent.setComponent(new ComponentName(mContext, DismissService.class));
+        PendingIntent pi = PendingIntent.getService(mContext, code, intent,
+                PendingIntent.FLAG_UPDATE_CURRENT);
+        return pi;
+    }
+
+    protected boolean checkEquals(long expected, long actual, String message) {
+        if (expected == actual) {
+            return true;
+        }
+        logWithStack(String.format(message, expected, actual));
+        return false;
+    }
+
+    protected boolean checkEquals(String expected, String actual, String message) {
+        if (expected.equals(actual)) {
+            return true;
+        }
+        logWithStack(String.format(message, expected, actual));
+        return false;
+    }
+
+    protected boolean checkFlagSet(int expected, int actual, String message) {
+        if ((expected & actual) != 0) {
+            return true;
+        }
+        logWithStack(String.format(message, expected, actual));
+        return false;
+    };
+
+    protected void logWithStack(String message) {
+        Throwable stackTrace = new Throwable();
+        stackTrace.fillInStackTrace();
+        Log.e(TAG, message, stackTrace);
+    }
+
+    // Common Tests: useful for the side-effects they generate
+
+    protected class IsEnabledTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createNlsSettingsItem(parent, R.string.nls_enable_service);
+        }
+
+        @Override
+        boolean autoStart() {
+            return true;
+        }
+
+        @Override
+        void test() {
+            Intent settings = new Intent(NOTIFICATION_LISTENER_SETTINGS);
+            if (settings.resolveActivity(mPackageManager) == null) {
+                logFail("no settings activity");
+                status = FAIL;
+            } else {
+                String listeners = Secure.getString(getContentResolver(),
+                        ENABLED_NOTIFICATION_LISTENERS);
+                if (listeners != null && listeners.contains(LISTENER_PATH)) {
+                    status = PASS;
+                } else {
+                    status = WAIT_FOR_USER;
+                }
+                next();
+            }
+        }
+
+        void tearDown() {
+            // wait for the service to start
+            delay();
+        }
+    }
+
+    protected class ServiceStartedTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.nls_service_started);
+        }
+
+        @Override
+        void test() {
+            MockListener.probeListenerStatus(mContext,
+                    new MockListener.StatusCatcher() {
+                        @Override
+                        public void accept(int result) {
+                            if (result == Activity.RESULT_OK) {
+                                status = PASS;
+                                next();
+                            } else {
+                                logFail();
+                                status = RETEST;
+                                delay();
+                            }
+                        }
+                    });
+            delay();  // in case the catcher never returns
+        }
+
+        @Override
+        void tearDown() {
+            MockListener.resetListenerData(mContext);
+            delay();
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockListener.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockListener.java
index b4863fa..7207083 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockListener.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockListener.java
@@ -82,6 +82,7 @@
         Log.d(TAG, "created");
 
         mTestPackages.add("com.android.cts.verifier");
+        mTestPackages.add("com.android.cts.robot");
 
         mPosted = new ArrayList<String>();
         mRemoved = new ArrayList<String>();
@@ -238,6 +239,7 @@
         Log.d(TAG, "removed: " + sbn.getTag());
         mRemoved.add(sbn.getTag());
         mNotifications.remove(sbn.getKey());
+        mNotificationKeys.remove(sbn.getTag());
         onNotificationRankingUpdate(rankingMap);
         mNotificationKeys.remove(sbn.getTag());
     }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationAttentionManagementVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationAttentionManagementVerifierActivity.java
deleted file mode 100644
index b4e348f..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationAttentionManagementVerifierActivity.java
+++ /dev/null
@@ -1,883 +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 com.android.cts.verifier.notifications;
-
-import static com.android.cts.verifier.notifications.MockListener.JSON_AMBIENT;
-import static com.android.cts.verifier.notifications.MockListener.JSON_MATCHES_ZEN_FILTER;
-import static com.android.cts.verifier.notifications.MockListener.JSON_TAG;
-
-import android.app.Activity;
-import android.app.Notification;
-import android.content.ContentProviderOperation;
-import android.content.Intent;
-import android.content.OperationApplicationException;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.CommonDataKinds.Email;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.CommonDataKinds.StructuredName;
-import android.provider.Settings.Secure;
-import android.service.notification.NotificationListenerService;
-import android.util.Log;
-import com.android.cts.verifier.R;
-import com.android.cts.verifier.nfc.TagVerifierActivity;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-public class NotificationAttentionManagementVerifierActivity
-        extends NotificationListenerVerifierActivity {
-    private static final String TAG = TagVerifierActivity.class.getSimpleName();
-    private static final String ALICE = "Alice";
-    private static final String ALICE_PHONE = "+16175551212";
-    private static final String ALICE_EMAIL = "alice@_foo._bar";
-    private static final String BOB = "Bob";
-    private static final String BOB_PHONE = "+16505551212";;
-    private static final String BOB_EMAIL = "bob@_foo._bar";
-    private static final String CHARLIE = "Charlie";
-    private static final String CHARLIE_PHONE = "+13305551212";
-    private static final String CHARLIE_EMAIL = "charlie@_foo._bar";
-    private static final int MODE_NONE = 0;
-    private static final int MODE_URI = 1;
-    private static final int MODE_PHONE = 2;
-    private static final int MODE_EMAIL = 3;
-    private static final int DELAYED_SETUP = CLEARED;
-
-    private Uri mAliceUri;
-    private Uri mBobUri;
-    private Uri mCharlieUri;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState, R.layout.nls_main);
-        setInfoResources(R.string.attention_test, R.string.attention_info, -1);
-    }
-
-    // Test Setup
-
-    @Override
-    protected void createTestItems() {
-        createNlsSettingsItem(R.string.nls_enable_service);
-        createAutoItem(R.string.nls_service_started);
-        createAutoItem(R.string.attention_create_contacts);
-        createRetryItem(R.string.attention_filter_none);
-        createAutoItem(R.string.attention_all_are_filtered);
-        createRetryItem(R.string.attention_filter_all);
-        createAutoItem(R.string.attention_none_are_filtered);
-        createAutoItem(R.string.attention_default_order);
-        createAutoItem(R.string.attention_interruption_order);
-        createAutoItem(R.string.attention_priority_order);
-        createAutoItem(R.string.attention_ambient_bit);
-        createAutoItem(R.string.attention_lookup_order);
-        createAutoItem(R.string.attention_email_order);
-        createAutoItem(R.string.attention_phone_order);
-        createRetryItem(R.string.attention_filter_priority);
-        createAutoItem(R.string.attention_some_are_filtered);
-        createAutoItem(R.string.attention_delete_contacts);
-    }
-
-    // Test management
-
-    @Override
-    protected void updateStateMachine() {
-        switch (mState) {
-            case 0:
-                testIsEnabled(mState);
-                break;
-            case 1:
-                testIsStarted(mState);
-                break;
-            case 2:
-                testInsertContacts(mState);
-                break;
-            case 3:
-                testModeNone(mState);
-                break;
-            case 4:
-                testNoneInterceptsAll(mState);
-                break;
-            case 5:
-                testModeAll(mState);
-                break;
-            case 6:
-                testAllInterceptsNothing(mState);
-                break;
-            case 7:
-                testDefaultOrder(mState);
-                break;
-            case 8:
-                testInterruptionOrder(mState);
-                break;
-            case 9:
-                testPrioritytOrder(mState);
-                break;
-            case 10:
-                testAmbientBits(mState);
-                break;
-            case 11:
-                testLookupUriOrder(mState);
-                break;
-            case 12:
-                testEmailOrder(mState);
-                break;
-            case 13:
-                testPhoneOrder(mState);
-                break;
-            case 14:
-                testModePriority(mState);
-                break;
-            case 15:
-                testPriorityInterceptsSome(mState);
-                break;
-            case 16:
-                testDeleteContacts(mState);
-                break;
-            case 17:
-                getPassButton().setEnabled(true);
-                mNm.cancelAll();
-                break;
-        }
-    }
-
-    // usePriorities true: B, C, A
-    // usePriorities false:
-    //   MODE_NONE: C, B, A
-    //   otherwise: A, B ,C
-    private void sendNotifications(int annotationMode, boolean usePriorities, boolean noisy) {
-        // TODO(cwren) Fixes flakey tests due to bug 17644321. Remove this line when it is fixed.
-        int baseId = NOTIFICATION_ID + (noisy ? 3 : 0);
-
-        // C, B, A when sorted by time.  Times must be in the past.
-        long whenA = System.currentTimeMillis() - 4000000L;
-        long whenB = System.currentTimeMillis() - 2000000L;
-        long whenC = System.currentTimeMillis() - 1000000L;
-
-        // B, C, A when sorted by priorities
-        int priorityA = usePriorities ? Notification.PRIORITY_MIN : Notification.PRIORITY_DEFAULT;
-        int priorityB = usePriorities ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT;
-        int priorityC = usePriorities ? Notification.PRIORITY_LOW : Notification.PRIORITY_DEFAULT;
-
-        Notification.Builder alice = new Notification.Builder(mContext)
-                .setContentTitle(ALICE)
-                .setContentText(ALICE)
-                .setSmallIcon(R.drawable.fs_good)
-                .setPriority(priorityA)
-                .setCategory(Notification.CATEGORY_MESSAGE)
-                .setWhen(whenA);
-        alice.setDefaults(noisy ? Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE : 0);
-        addPerson(annotationMode, alice, mAliceUri, ALICE_PHONE, ALICE_EMAIL);
-        mNm.notify(ALICE, baseId + 1, alice.build());
-
-        Notification.Builder bob = new Notification.Builder(mContext)
-                .setContentTitle(BOB)
-                .setContentText(BOB)
-                .setSmallIcon(R.drawable.fs_warning)
-                .setPriority(priorityB)
-                .setCategory(Notification.CATEGORY_MESSAGE)
-                .setWhen(whenB);
-        addPerson(annotationMode, bob, mBobUri, BOB_PHONE, BOB_EMAIL);
-        mNm.notify(BOB, baseId + 2, bob.build());
-
-        Notification.Builder charlie = new Notification.Builder(mContext)
-                .setContentTitle(CHARLIE)
-                .setContentText(CHARLIE)
-                .setSmallIcon(R.drawable.fs_error)
-                .setPriority(priorityC)
-                .setCategory(Notification.CATEGORY_MESSAGE)
-                .setWhen(whenC);
-        addPerson(annotationMode, charlie, mCharlieUri, CHARLIE_PHONE, CHARLIE_EMAIL);
-        mNm.notify(CHARLIE, baseId + 3, charlie.build());
-    }
-
-    private void addPerson(int mode, Notification.Builder note,
-            Uri uri, String phone, String email) {
-        if (mode == MODE_URI && uri != null) {
-            note.addPerson(uri.toString());
-        } else if (mode == MODE_PHONE) {
-            note.addPerson(Uri.fromParts("tel", phone, null).toString());
-        } else if (mode == MODE_EMAIL) {
-            note.addPerson(Uri.fromParts("mailto", email, null).toString());
-        }
-    }
-
-    // Tests
-
-    private void testIsEnabled(int i) {
-        // no setup required
-        Intent settings = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
-        if (settings.resolveActivity(mPackageManager) == null) {
-            logWithStack("failed testIsEnabled: no settings activity");
-            mStatus[i] = FAIL;
-        } else {
-            // TODO: find out why Secure.ENABLED_NOTIFICATION_LISTENERS is hidden
-            String listeners = Secure.getString(getContentResolver(),
-                    "enabled_notification_listeners");
-            if (listeners != null && listeners.contains(LISTENER_PATH)) {
-                mStatus[i] = PASS;
-            } else {
-                mStatus[i] = WAIT_FOR_USER;
-            }
-        }
-        next();
-    }
-
-    private void testIsStarted(final int i) {
-        if (mStatus[i] == SETUP) {
-            mStatus[i] = READY;
-            // wait for the service to start
-            delay();
-        } else {
-            MockListener.probeListenerStatus(mContext,
-                    new MockListener.StatusCatcher() {
-                        @Override
-                        public void accept(int result) {
-                            if (result == Activity.RESULT_OK) {
-                                mStatus[i] = PASS;
-                            } else {
-                                logWithStack("failed testIsStarted: " + result);
-                                mStatus[i] = FAIL;
-                            }
-                            next();
-                        }
-                    });
-        }
-    }
-
-    private void testModeAll(final int i) {
-        if (mStatus[i] == READY || mStatus[i] == SETUP) {
-            MockListener.probeFilter(mContext,
-                    new MockListener.IntegerResultCatcher() {
-                        @Override
-                        public void accept(int mode) {
-                            if (mode == NotificationListenerService.INTERRUPTION_FILTER_ALL) {
-                                mStatus[i] = PASS;
-                            } else {
-                                logWithStack("waiting testModeAll: " + mode);
-                                mStatus[i] = WAIT_FOR_USER;
-                            }
-                            next();
-                        }
-                    });
-        }
-    }
-
-    private void testModePriority(final int i) {
-        if (mStatus[i] == READY || mStatus[i] == SETUP) {
-            MockListener.probeFilter(mContext,
-                    new MockListener.IntegerResultCatcher() {
-                        @Override
-                        public void accept(int mode) {
-                            if (mode == NotificationListenerService.INTERRUPTION_FILTER_PRIORITY) {
-                                mStatus[i] = PASS;
-                            } else {
-                                logWithStack("waiting testModePriority: " + mode);
-                                mStatus[i] = WAIT_FOR_USER;
-                            }
-                            next();
-                        }
-                    });
-        }
-    }
-
-    private void testModeNone(final int i) {
-        if (mStatus[i] == READY || mStatus[i] == SETUP) {
-            MockListener.probeFilter(mContext,
-                    new MockListener.IntegerResultCatcher() {
-                        @Override
-                        public void accept(int mode) {
-                            if (mode == NotificationListenerService.INTERRUPTION_FILTER_NONE) {
-                                mStatus[i] = PASS;
-                            } else {
-                                logWithStack("waiting testModeNone: " + mode);
-                                mStatus[i] = WAIT_FOR_USER;
-                            }
-                            next();
-                        }
-                    });
-        }
-    }
-
-
-    private void insertSingleContact(String name, String phone, String email, boolean starred) {
-        final ArrayList<ContentProviderOperation> operationList =
-                new ArrayList<ContentProviderOperation>();
-        ContentProviderOperation.Builder builder =
-                ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI);
-        builder.withValue(ContactsContract.RawContacts.STARRED, starred ? 1 : 0);
-        operationList.add(builder.build());
-
-        builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI);
-        builder.withValueBackReference(StructuredName.RAW_CONTACT_ID, 0);
-        builder.withValue(ContactsContract.Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
-        builder.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name);
-        operationList.add(builder.build());
-
-        if (phone != null) {
-            builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI);
-            builder.withValueBackReference(Phone.RAW_CONTACT_ID, 0);
-            builder.withValue(ContactsContract.Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
-            builder.withValue(Phone.TYPE, Phone.TYPE_MOBILE);
-            builder.withValue(Phone.NUMBER, phone);
-            builder.withValue(ContactsContract.Data.IS_PRIMARY, 1);
-            operationList.add(builder.build());
-        }
-        if (email != null) {
-            builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI);
-            builder.withValueBackReference(Email.RAW_CONTACT_ID, 0);
-            builder.withValue(ContactsContract.Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
-            builder.withValue(Email.TYPE, Email.TYPE_HOME);
-            builder.withValue(Email.DATA, email);
-            operationList.add(builder.build());
-        }
-
-        try {
-            mContext.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operationList);
-        } catch (RemoteException e) {
-            Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
-        } catch (OperationApplicationException e) {
-            Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
-        }
-    }
-
-    private Uri lookupContact(String phone) {
-        Cursor c = null;
-        try {
-            Uri phoneUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
-                    Uri.encode(phone));
-            String[] projection = new String[] { ContactsContract.Contacts._ID,
-                    ContactsContract.Contacts.LOOKUP_KEY };
-            c = mContext.getContentResolver().query(phoneUri, projection, null, null, null);
-            if (c != null && c.getCount() > 0) {
-                c.moveToFirst();
-                int lookupIdx = c.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY);
-                int idIdx = c.getColumnIndex(ContactsContract.Contacts._ID);
-                String lookupKey = c.getString(lookupIdx);
-                long contactId = c.getLong(idIdx);
-                return ContactsContract.Contacts.getLookupUri(contactId, lookupKey);
-            }
-        } catch (Throwable t) {
-            Log.w(TAG, "Problem getting content resolver or performing contacts query.", t);
-        } finally {
-            if (c != null) {
-                c.close();
-            }
-        }
-        return null;
-    }
-
-    private void testInsertContacts(final int i) {
-        if (mStatus[i] == SETUP) {
-            insertSingleContact(ALICE, ALICE_PHONE, ALICE_EMAIL, true);
-            insertSingleContact(BOB, BOB_PHONE, BOB_EMAIL, false);
-            // charlie is not in contacts
-            mStatus[i] = READY;
-            // wait for insertions to move through the system
-            delay();
-        } else {
-            mAliceUri = lookupContact(ALICE_PHONE);
-            mBobUri = lookupContact(BOB_PHONE);
-            mCharlieUri = lookupContact(CHARLIE_PHONE);
-
-            mStatus[i] = PASS;
-            if (mAliceUri == null) { mStatus[i] = FAIL; }
-            if (mBobUri == null) { mStatus[i] = FAIL; }
-            if (mCharlieUri != null) { mStatus[i] = FAIL; }
-            next();
-        }
-    }
-
-    // ordered by time: C, B, A
-    private void testDefaultOrder(final int i) {
-        if (mStatus[i] == SETUP) {
-            mNm.cancelAll();
-            MockListener.resetListenerData(this);
-            mStatus[i] = CLEARED;
-            // wait for intent to move through the system
-            delay();
-        } else if (mStatus[i] == CLEARED) {
-            sendNotifications(MODE_NONE, false, false);
-            mStatus[i] = READY;
-            // wait for notifications to move through the system
-            delay();
-        } else {
-            MockListener.probeListenerOrder(mContext,
-                    new MockListener.StringListResultCatcher() {
-                        @Override
-                        public void accept(List<String> orderedKeys) {
-                            int rankA = findTagInKeys(ALICE, orderedKeys);
-                            int rankB = findTagInKeys(BOB, orderedKeys);
-                            int rankC = findTagInKeys(CHARLIE, orderedKeys);
-                            if (rankC < rankB && rankB < rankA) {
-                                mStatus[i] = PASS;
-                            } else {
-                                logWithStack("failed testDefaultOrder : "
-                                        + rankA + ", " + rankB + ", " + rankC);
-                                mStatus[i] = FAIL;
-                            }
-                            next();
-                        }
-                    });
-        }
-    }
-
-    // ordered by priority: B, C, A
-    private void testPrioritytOrder(final int i) {
-        if (mStatus[i] == SETUP) {
-            mNm.cancelAll();
-            MockListener.resetListenerData(this);
-            mStatus[i] = CLEARED;
-            // wait for intent to move through the system
-            delay();
-        } else if (mStatus[i] == CLEARED) {
-            sendNotifications(MODE_PHONE, true, false);
-            mStatus[i] = READY;
-            // wait for notifications to move through the system
-            delay();
-        } else {
-            MockListener.probeListenerOrder(mContext,
-                    new MockListener.StringListResultCatcher() {
-                        @Override
-                        public void accept(List<String> orderedKeys) {
-                            int rankA = findTagInKeys(ALICE, orderedKeys);
-                            int rankB = findTagInKeys(BOB, orderedKeys);
-                            int rankC = findTagInKeys(CHARLIE, orderedKeys);
-                            if (rankB < rankC && rankC < rankA) {
-                                mStatus[i] = PASS;
-                            } else {
-                                logWithStack("failed testPrioritytOrder : "
-                                        + rankA + ", " + rankB + ", " + rankC);
-                                mStatus[i] = FAIL;
-                            }
-                            next();
-                        }
-                    });
-        }
-    }
-
-    // B & C above the fold, A below
-    private void testAmbientBits(final int i) {
-        if (mStatus[i] == SETUP) {
-            mNm.cancelAll();
-            MockListener.resetListenerData(this);
-            mStatus[i] = CLEARED;
-            // wait for intent to move through the system
-            delay();
-        } else if (mStatus[i] == CLEARED) {
-            sendNotifications(MODE_PHONE, true, false);
-            mStatus[i] = READY;
-            // wait for notifications to move through the system
-            delay();
-        } else {
-            MockListener.probeListenerPayloads(mContext,
-                    new MockListener.StringListResultCatcher() {
-                        @Override
-                        public void accept(List<String> result) {
-                            boolean pass = false;
-                            Set<String> found = new HashSet<String>();
-                            if (result != null && result.size() > 0) {
-                                pass = true;
-                                for (String payloadData : result) {
-                                    try {
-                                        JSONObject payload = new JSONObject(payloadData);
-                                        String tag = payload.getString(JSON_TAG);
-                                        if (found.contains(tag)) {
-                                            // multiple entries for same notification!
-                                            pass = false;
-                                        } else if (ALICE.equals(tag)) {
-                                            found.add(ALICE);
-                                            pass &= payload.getBoolean(JSON_AMBIENT);
-                                        } else if (BOB.equals(tag)) {
-                                            found.add(BOB);
-                                            pass &= !payload.getBoolean(JSON_AMBIENT);
-                                        } else if (CHARLIE.equals(tag)) {
-                                            found.add(CHARLIE);
-                                            pass &= !payload.getBoolean(JSON_AMBIENT);
-                                        }
-                                    } catch (JSONException e) {
-                                        pass = false;
-                                        Log.e(TAG, "failed to unpack data from mocklistener", e);
-                                    }
-                                }
-                            }
-                            pass &= found.size() == 3;
-                            mStatus[i] = pass ? PASS : FAIL;
-                            next();
-                        }
-                    });
-        }
-    }
-
-    // ordered by contact affinity: A, B, C
-    private void testLookupUriOrder(final int i) {
-        if (mStatus[i] == SETUP) {
-            mNm.cancelAll();
-            MockListener.resetListenerData(this);
-            mStatus[i] = CLEARED;
-            // wait for intent to move through the system
-            delay();
-        } else if (mStatus[i] == CLEARED) {
-            sendNotifications(MODE_URI, false, false);
-            mStatus[i] = READY;
-            // wait for notifications to move through the system
-            delay();
-        } else {
-            MockListener.probeListenerOrder(mContext,
-                    new MockListener.StringListResultCatcher() {
-                        @Override
-                        public void accept(List<String> orderedKeys) {
-                            int rankA = findTagInKeys(ALICE, orderedKeys);
-                            int rankB = findTagInKeys(BOB, orderedKeys);
-                            int rankC = findTagInKeys(CHARLIE, orderedKeys);
-                            if (rankA < rankB && rankB < rankC) {
-                                mStatus[i] = PASS;
-                            } else {
-                                logWithStack("failed testLookupUriOrder : "
-                                        + rankA + ", " + rankB + ", " + rankC);
-                                mStatus[i] = FAIL;
-                            }
-                            next();
-                        }
-                    });
-        }
-    }
-
-    // ordered by contact affinity: A, B, C
-    private void testEmailOrder(final int i) {
-        if (mStatus[i] == SETUP) {
-            mNm.cancelAll();
-            MockListener.resetListenerData(this);
-            mStatus[i] = DELAYED_SETUP;
-            // wait for intent to move through the system
-            delay();
-        } else if (mStatus[i] == DELAYED_SETUP) {
-            sendNotifications(MODE_EMAIL, false, false);
-            mStatus[i] = READY;
-            // wait for notifications to move through the system
-            delay();
-        } else {
-            MockListener.probeListenerOrder(mContext,
-                    new MockListener.StringListResultCatcher() {
-                        @Override
-                        public void accept(List<String> orderedKeys) {
-                            int rankA = findTagInKeys(ALICE, orderedKeys);
-                            int rankB = findTagInKeys(BOB, orderedKeys);
-                            int rankC = findTagInKeys(CHARLIE, orderedKeys);
-                            if (rankA < rankB && rankB < rankC) {
-                                mStatus[i] = PASS;
-                            } else {
-                                logWithStack("failed testEmailOrder : "
-                                        + rankA + ", " + rankB + ", " + rankC);
-                                mStatus[i] = FAIL;
-                            }
-                            next();
-                        }
-                    });
-        }
-    }
-
-    // ordered by contact affinity: A, B, C
-    private void testPhoneOrder(final int i) {
-        if (mStatus[i] == SETUP) {
-            mNm.cancelAll();
-            MockListener.resetListenerData(this);
-            mStatus[i] = CLEARED;
-            // wait for intent to move through the system
-            delay();
-        } else if (mStatus[i] == CLEARED) {
-            sendNotifications(MODE_PHONE, false, false);
-            mStatus[i] = READY;
-            // wait for notifications to move through the system
-            delay();
-        } else {
-            MockListener.probeListenerOrder(mContext,
-                    new MockListener.StringListResultCatcher() {
-                        @Override
-                        public void accept(List<String> orderedKeys) {
-                            int rankA = findTagInKeys(ALICE, orderedKeys);
-                            int rankB = findTagInKeys(BOB, orderedKeys);
-                            int rankC = findTagInKeys(CHARLIE, orderedKeys);
-                            if (rankA < rankB && rankB < rankC) {
-                                mStatus[i] = PASS;
-                            } else {
-                                logWithStack("failed testPhoneOrder : "
-                                        + rankA + ", " + rankB + ", " + rankC);
-                                mStatus[i] = FAIL;
-                            }
-                            next();
-                        }
-                    });
-        }
-    }
-
-    // A starts at the top then falls to the bottom
-    private void testInterruptionOrder(final int i) {
-        if (mStatus[i] == SETUP) {
-            mNm.cancelAll();
-            MockListener.resetListenerData(this);
-            mStatus[i] = CLEARED;
-            // wait for intent to move through the system
-            delay();
-        } else if (mStatus[i] == CLEARED) {
-            sendNotifications(MODE_NONE, false, true);
-            mStatus[i] = READY;
-            // wait for notifications to move through the system
-            delay();
-        } else if (mStatus[i] == READY) {
-            MockListener.probeListenerOrder(mContext,
-                    new MockListener.StringListResultCatcher() {
-                        @Override
-                        public void accept(List<String> orderedKeys) {
-                            int rankA = findTagInKeys(ALICE, orderedKeys);
-                            int rankB = findTagInKeys(BOB, orderedKeys);
-                            int rankC = findTagInKeys(CHARLIE, orderedKeys);
-                            if (rankA < rankB && rankA < rankC) {
-                                mStatus[i] = RETRY;
-                                delay(12000);
-                            } else {
-                                logWithStack("noisy notification did not sort to top.");
-                                mStatus[i] = FAIL;
-                                next();
-                            }
-                        }
-                    });
-        } else if (mStatus[i] == RETRY) {
-            MockListener.probeListenerOrder(mContext,
-                    new MockListener.StringListResultCatcher() {
-                        @Override
-                        public void accept(List<String> orderedKeys) {
-                            int rankA = findTagInKeys(ALICE, orderedKeys);
-                            int rankB = findTagInKeys(BOB, orderedKeys);
-                            int rankC = findTagInKeys(CHARLIE, orderedKeys);
-                            if (rankA > rankB && rankA > rankC) {
-                                mStatus[i] = PASS;
-                            } else {
-                                logWithStack("noisy notification did not fade back into the list.");
-                                mStatus[i] = FAIL;
-                            }
-                            next();
-                        }
-                    });
-        }
-    }
-
-    // Nothing should be filtered when mode is ALL
-    private void testAllInterceptsNothing(final int i) {
-        if (mStatus[i] == SETUP) {
-            mNm.cancelAll();
-            MockListener.resetListenerData(this);
-            mStatus[i] = CLEARED;
-            // wait for intent to move through the system
-            delay();
-        } else if (mStatus[i] == CLEARED) {
-            sendNotifications(MODE_URI, false, false);
-            mStatus[i] = READY;
-            // wait for notifications to move through the system
-            delay();
-        } else {
-            MockListener.probeListenerPayloads(mContext,
-                    new MockListener.StringListResultCatcher() {
-                        @Override
-                        public void accept(List<String> result) {
-                            boolean pass = false;
-                            Set<String> found = new HashSet<String>();
-                            if (result != null && result.size() > 0) {
-                                pass = true;
-                                for (String payloadData : result) {
-                                    try {
-                                        JSONObject payload = new JSONObject(payloadData);
-                                        String tag = payload.getString(JSON_TAG);
-                                        if (found.contains(tag)) {
-                                            // multiple entries for same notification!
-                                            pass = false;
-                                        } else if (ALICE.equals(tag)) {
-                                            found.add(ALICE);
-                                            pass &= payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
-                                        } else if (BOB.equals(tag)) {
-                                            found.add(BOB);
-                                            pass &= payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
-                                        } else if (CHARLIE.equals(tag)) {
-                                            found.add(CHARLIE);
-                                            pass &= payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
-                                        }
-                                    } catch (JSONException e) {
-                                        pass = false;
-                                        Log.e(TAG, "failed to unpack data from mocklistener", e);
-                                    }
-                                }
-                            }
-                            pass &= found.size() == 3;
-                            mStatus[i] = pass ? PASS : FAIL;
-                            next();
-                        }
-                    });
-        }
-    }
-
-    // A should be filtered when mode is Priority/Starred.
-    private void testPriorityInterceptsSome(final int i) {
-        if (mStatus[i] == SETUP) {
-            mNm.cancelAll();
-            MockListener.resetListenerData(this);
-            mStatus[i] = CLEARED;
-            // wait for intent to move through the system
-            delay();
-        } else if (mStatus[i] == CLEARED) {
-            sendNotifications(MODE_URI, false, false);
-            mStatus[i] = READY;
-            // wait for notifications to move through the system
-            delay();
-        } else {
-            MockListener.probeListenerPayloads(mContext,
-                    new MockListener.StringListResultCatcher() {
-                        @Override
-                        public void accept(List<String> result) {
-                            boolean pass = false;
-                            Set<String> found = new HashSet<String>();
-                            if (result != null && result.size() > 0) {
-                                pass = true;
-                                for (String payloadData : result) {
-                                    try {
-                                        JSONObject payload = new JSONObject(payloadData);
-                                        String tag = payload.getString(JSON_TAG);
-                                        if (found.contains(tag)) {
-                                            // multiple entries for same notification!
-                                            pass = false;
-                                        } else if (ALICE.equals(tag)) {
-                                            found.add(ALICE);
-                                            pass &= payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
-                                        } else if (BOB.equals(tag)) {
-                                            found.add(BOB);
-                                            pass &= !payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
-                                        } else if (CHARLIE.equals(tag)) {
-                                            found.add(CHARLIE);
-                                            pass &= !payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
-                                        }
-                                    } catch (JSONException e) {
-                                        pass = false;
-                                        Log.e(TAG, "failed to unpack data from mocklistener", e);
-                                    }
-                                }
-                            }
-                            pass &= found.size() == 3;
-                            mStatus[i] = pass ? PASS : FAIL;
-                            next();
-                        }
-                    });
-        }
-    }
-
-    // Nothing should get through when mode is None.
-    private void testNoneInterceptsAll(final int i) {
-        if (mStatus[i] == SETUP) {
-            mNm.cancelAll();
-            MockListener.resetListenerData(this);
-            mStatus[i] = CLEARED;
-            // wait for intent to move through the system
-            delay();
-        } else if (mStatus[i] == CLEARED) {
-            sendNotifications(MODE_URI, false, false);
-            mStatus[i] = READY;
-            // wait for notifications to move through the system
-            delay();
-        } else {
-            MockListener.probeListenerPayloads(mContext,
-                    new MockListener.StringListResultCatcher() {
-                        @Override
-                        public void accept(List<String> result) {
-                            boolean pass = false;
-                            Set<String> found = new HashSet<String>();
-                            if (result != null && result.size() > 0) {
-                                pass = true;
-                                for (String payloadData : result) {
-                                    try {
-                                        JSONObject payload = new JSONObject(payloadData);
-                                        String tag = payload.getString(JSON_TAG);
-                                        if (found.contains(tag)) {
-                                            // multiple entries for same notification!
-                                            pass = false;
-                                        } else if (ALICE.equals(tag)) {
-                                            found.add(ALICE);
-                                            pass &= !payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
-                                        } else if (BOB.equals(tag)) {
-                                            found.add(BOB);
-                                            pass &= !payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
-                                        } else if (CHARLIE.equals(tag)) {
-                                            found.add(CHARLIE);
-                                            pass &= !payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
-                                        }
-                                    } catch (JSONException e) {
-                                        pass = false;
-                                        Log.e(TAG, "failed to unpack data from mocklistener", e);
-                                    }
-                                }
-                            }
-                            pass &= found.size() == 3;
-                            mStatus[i] = pass ? PASS : FAIL;
-                            next();
-                        }
-                    });
-        }
-    }
-
-    /** Search a list of notification keys for a givcen tag. */
-    private int findTagInKeys(String tag, List<String> orderedKeys) {
-        for (int i = 0; i < orderedKeys.size(); i++) {
-            if (orderedKeys.get(i).contains(tag)) {
-                return i;
-            }
-        }
-        return -1;
-    }
-
-    private void testDeleteContacts(final int i) {
-        if (mStatus[i] == SETUP) {
-            final ArrayList<ContentProviderOperation> operationList =
-                    new ArrayList<ContentProviderOperation>();
-            operationList.add(ContentProviderOperation.newDelete(mAliceUri).build());
-            operationList.add(ContentProviderOperation.newDelete(mBobUri).build());
-            try {
-                mContext.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operationList);
-                mStatus[i] = READY;
-            } catch (RemoteException e) {
-                Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
-                mStatus[i] = FAIL;
-            } catch (OperationApplicationException e) {
-                Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
-                mStatus[i] = FAIL;
-            }
-            // wait for deletions to move through the system
-            delay(3000);
-        } else if (mStatus[i] == READY) {
-            mAliceUri = lookupContact(ALICE_PHONE);
-            mBobUri = lookupContact(BOB_PHONE);
-            mCharlieUri = lookupContact(CHARLIE_PHONE);
-
-            mStatus[i] = PASS;
-            if (mAliceUri != null) { mStatus[i] = FAIL; }
-            if (mBobUri != null) { mStatus[i] = FAIL; }
-            if (mCharlieUri != null) { mStatus[i] = FAIL; }
-            next();
-        }
-    }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
index 0ef595b..ace194c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
@@ -16,76 +16,30 @@
 
 package com.android.cts.verifier.notifications;
 
-import static com.android.cts.verifier.notifications.MockListener.JSON_FLAGS;
-import static com.android.cts.verifier.notifications.MockListener.JSON_ICON;
-import static com.android.cts.verifier.notifications.MockListener.JSON_ID;
-import static com.android.cts.verifier.notifications.MockListener.JSON_PACKAGE;
-import static com.android.cts.verifier.notifications.MockListener.JSON_TAG;
-import static com.android.cts.verifier.notifications.MockListener.JSON_WHEN;
-
 import android.annotation.SuppressLint;
 import android.app.Activity;
 import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.Service;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.os.Bundle;
-import android.os.IBinder;
 import android.provider.Settings.Secure;
 import android.util.Log;
-import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.TextView;
 
-import com.android.cts.verifier.PassFailButtons;
 import com.android.cts.verifier.R;
-import com.android.cts.verifier.nfc.TagVerifierActivity;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 
+import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.UUID;
-import java.util.concurrent.LinkedBlockingQueue;
 
-public class NotificationListenerVerifierActivity extends PassFailButtons.Activity
-implements Runnable {
-    private static final String TAG = TagVerifierActivity.class.getSimpleName();
-    private static final String STATE = "state";
-    private static LinkedBlockingQueue<String> sDeletedQueue = new LinkedBlockingQueue<String>();
+import static com.android.cts.verifier.notifications.MockListener.*;
 
-    protected static final String LISTENER_PATH = "com.android.cts.verifier/" +
-            "com.android.cts.verifier.notifications.MockListener";
-    protected static final int SETUP = 0;
-    protected static final int PASS = 1;
-    protected static final int FAIL = 2;
-    protected static final int WAIT_FOR_USER = 3;
-    protected static final int CLEARED = 4;
-    protected static final int READY = 5;
-    protected static final int RETRY = 6;
-
-    protected static final int NOTIFICATION_ID = 1001;
-
-    protected int mState;
-    protected int[] mStatus;
-    protected PackageManager mPackageManager;
-    protected NotificationManager mNm;
-    protected Context mContext;
-    protected Runnable mRunner;
-    protected View mHandler;
-    protected String mPackageString;
-
-    private LayoutInflater mInflater;
-    private ViewGroup mItemList;
+public class NotificationListenerVerifierActivity extends InteractiveVerifierActivity
+        implements Runnable {
+    private static final String TAG = "NoListenerVerifier";
 
     private String mTag1;
     private String mTag2;
@@ -103,199 +57,31 @@
     private int mFlag2;
     private int mFlag3;
 
-    public static class DismissService extends Service {
-        @Override
-        public IBinder onBind(Intent intent) {
-            return null;
-        }
-
-        @Override
-        public void onStart(Intent intent, int startId) {
-            sDeletedQueue.offer(intent.getAction());
-        }
+    @Override
+    int getTitleResource() {
+        return R.string.nls_test;
     }
 
     @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        onCreate(savedInstanceState, R.layout.nls_main);
-        setInfoResources(R.string.nls_test, R.string.nls_info, -1);
+    int getInstructionsResource() {
+        return R.string.nls_info;
     }
 
-    protected void onCreate(Bundle savedInstanceState, int layoutId) {
-        super.onCreate(savedInstanceState);
-
-        if (savedInstanceState != null) {
-            mState = savedInstanceState.getInt(STATE, 0);
-        }
-        mContext = this;
-        mRunner = this;
-        mNm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
-        mPackageManager = getPackageManager();
-        mInflater = getLayoutInflater();
-        View view = mInflater.inflate(layoutId, null);
-        mItemList = (ViewGroup) view.findViewById(R.id.nls_test_items);
-        mHandler = mItemList;
-        createTestItems();
-        mStatus = new int[mItemList.getChildCount()];
-        setContentView(view);
-
-        setPassFailButtonClickListeners();
-        getPassButton().setEnabled(false);
-    }
+    // Test Setup
 
     @Override
-    protected void onSaveInstanceState (Bundle outState) {
-        outState.putInt(STATE, mState);
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-        next();
-    }
-
-    // Interface Utilities
-
-    protected void createTestItems() {
-        createNlsSettingsItem(R.string.nls_enable_service);
-        createAutoItem(R.string.nls_service_started);
-        createAutoItem(R.string.nls_note_received);
-        createAutoItem(R.string.nls_payload_intact);
-        createAutoItem(R.string.nls_clear_one);
-        createAutoItem(R.string.nls_clear_all);
-        createNlsSettingsItem(R.string.nls_disable_service);
-        createAutoItem(R.string.nls_service_stopped);
-        createAutoItem(R.string.nls_note_missed);
-    }
-
-    protected void setItemState(int index, boolean passed) {
-        ViewGroup item = (ViewGroup) mItemList.getChildAt(index);
-        ImageView status = (ImageView) item.findViewById(R.id.nls_status);
-        status.setImageResource(passed ? R.drawable.fs_good : R.drawable.fs_error);
-        View button = item.findViewById(R.id.nls_action_button);
-        button.setClickable(false);
-        button.setEnabled(false);
-        status.invalidate();
-    }
-
-    protected void markItemWaiting(int index) {
-        ViewGroup item = (ViewGroup) mItemList.getChildAt(index);
-        ImageView status = (ImageView) item.findViewById(R.id.nls_status);
-        status.setImageResource(R.drawable.fs_warning);
-        status.invalidate();
-    }
-
-    protected View createNlsSettingsItem(int messageId) {
-        return createUserItem(messageId, R.string.nls_start_settings);
-    }
-
-    protected View createRetryItem(int messageId) {
-        return createUserItem(messageId, R.string.attention_ready);
-    }
-
-    protected View createUserItem(int messageId, int actionId) {
-        View item = mInflater.inflate(R.layout.nls_item, mItemList, false);
-        TextView instructions = (TextView) item.findViewById(R.id.nls_instructions);
-        instructions.setText(messageId);
-        Button button = (Button) item.findViewById(R.id.nls_action_button);
-        button.setText(actionId);
-        mItemList.addView(item);
-        button.setTag(actionId);
-        return item;
-    }
-
-    protected View createAutoItem(int stringId) {
-        View item = mInflater.inflate(R.layout.nls_item, mItemList, false);
-        TextView instructions = (TextView) item.findViewById(R.id.nls_instructions);
-        instructions.setText(stringId);
-        View button = item.findViewById(R.id.nls_action_button);
-        button.setVisibility(View.GONE);
-        mItemList.addView(item);
-        return item;
-    }
-
-    // Test management
-
-    public void run() {
-        while (mState < mStatus.length && mStatus[mState] != WAIT_FOR_USER) {
-            if (mStatus[mState] == PASS) {
-                setItemState(mState, true);
-                mState++;
-            } else if (mStatus[mState] == FAIL) {
-                setItemState(mState, false);
-                return;
-            } else {
-                break;
-            }
-        }
-
-        if (mState < mStatus.length && mStatus[mState] == WAIT_FOR_USER) {
-            markItemWaiting(mState);
-        }
-
-        updateStateMachine();
-    }
-
-    protected void updateStateMachine() {
-        switch (mState) {
-            case 0:
-                testIsEnabled(mState);
-                break;
-            case 1:
-                testIsStarted(mState);
-                break;
-            case 2:
-                testNotificationRecieved(mState);
-                break;
-            case 3:
-                testDataIntact(mState);
-                break;
-            case 4:
-                testDismissOne(mState);
-                break;
-            case 5:
-                testDismissAll(mState);
-                break;
-            case 6:
-                testIsDisabled(mState);
-                break;
-            case 7:
-                testIsStopped(mState);
-                break;
-            case 8:
-                testNotificationNotRecieved(mState);
-                break;
-            case 9:
-                getPassButton().setEnabled(true);
-                mNm.cancelAll();
-                break;
-        }
-    }
-
-    public void launchSettings() {
-        startActivity(
-                new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"));
-    }
-
-    public void actionPressed(View v) {
-        Object tag = v.getTag();
-        if (tag instanceof Integer) {
-            int id = ((Integer) tag).intValue();
-            if (id == R.string.nls_start_settings) {
-                launchSettings();
-            } else if (id == R.string.attention_ready) {
-                mStatus[mState] = READY;
-                next();
-            }
-        }
-    }
-
-    protected PendingIntent makeIntent(int code, String tag) {
-        Intent intent = new Intent(tag);
-        intent.setComponent(new ComponentName(mContext, DismissService.class));
-        PendingIntent pi = PendingIntent.getService(mContext, code, intent,
-                PendingIntent.FLAG_UPDATE_CURRENT);
-        return pi;
+    protected List<InteractiveTestCase> createTestItems() {
+        List<InteractiveTestCase> tests = new ArrayList<>(9);
+        tests.add(new IsEnabledTest());
+        tests.add(new ServiceStartedTest());
+        tests.add(new NotificationRecievedTest());
+        tests.add(new DataIntactTest());
+        tests.add(new DismissOneTest());
+        tests.add(new DismissAllTest());
+        tests.add(new IsDisabledTest());
+        tests.add(new ServiceStoppedTest());
+        tests.add(new NotificationNotReceivedTest());
+        return tests;
     }
 
     @SuppressLint("NewApi")
@@ -310,9 +96,9 @@
         mWhen2 = System.currentTimeMillis() + 2;
         mWhen3 = System.currentTimeMillis() + 3;
 
-        mIcon1 = R.drawable.fs_good;
-        mIcon2 = R.drawable.fs_error;
-        mIcon3 = R.drawable.fs_warning;
+        mIcon1 = R.drawable.ic_stat_alice;
+        mIcon2 = R.drawable.ic_stat_bob;
+        mIcon3 = R.drawable.ic_stat_charlie;
 
         mId1 = NOTIFICATION_ID + 1;
         mId2 = NOTIFICATION_ID + 2;
@@ -321,356 +107,352 @@
         mPackageString = "com.android.cts.verifier";
 
         Notification n1 = new Notification.Builder(mContext)
-        .setContentTitle("ClearTest 1")
-        .setContentText(mTag1.toString())
-        .setPriority(Notification.PRIORITY_LOW)
-        .setSmallIcon(mIcon1)
-        .setWhen(mWhen1)
-        .setDeleteIntent(makeIntent(1, mTag1))
-        .setOnlyAlertOnce(true)
-        .build();
+                .setContentTitle("ClearTest 1")
+                .setContentText(mTag1.toString())
+                .setPriority(Notification.PRIORITY_LOW)
+                .setSmallIcon(mIcon1)
+                .setWhen(mWhen1)
+                .setDeleteIntent(makeIntent(1, mTag1))
+                .setOnlyAlertOnce(true)
+                .build();
         mNm.notify(mTag1, mId1, n1);
         mFlag1 = Notification.FLAG_ONLY_ALERT_ONCE;
 
         Notification n2 = new Notification.Builder(mContext)
-        .setContentTitle("ClearTest 2")
-        .setContentText(mTag2.toString())
-        .setPriority(Notification.PRIORITY_HIGH)
-        .setSmallIcon(mIcon2)
-        .setWhen(mWhen2)
-        .setDeleteIntent(makeIntent(2, mTag2))
-        .setAutoCancel(true)
-        .build();
+                .setContentTitle("ClearTest 2")
+                .setContentText(mTag2.toString())
+                .setPriority(Notification.PRIORITY_HIGH)
+                .setSmallIcon(mIcon2)
+                .setWhen(mWhen2)
+                .setDeleteIntent(makeIntent(2, mTag2))
+                .setAutoCancel(true)
+                .build();
         mNm.notify(mTag2, mId2, n2);
         mFlag2 = Notification.FLAG_AUTO_CANCEL;
 
         Notification n3 = new Notification.Builder(mContext)
-        .setContentTitle("ClearTest 3")
-        .setContentText(mTag3.toString())
-        .setPriority(Notification.PRIORITY_LOW)
-        .setSmallIcon(mIcon3)
-        .setWhen(mWhen3)
-        .setDeleteIntent(makeIntent(3, mTag3))
-        .setAutoCancel(true)
-        .setOnlyAlertOnce(true)
-        .build();
+                .setContentTitle("ClearTest 3")
+                .setContentText(mTag3.toString())
+                .setPriority(Notification.PRIORITY_LOW)
+                .setSmallIcon(mIcon3)
+                .setWhen(mWhen3)
+                .setDeleteIntent(makeIntent(3, mTag3))
+                .setAutoCancel(true)
+                .setOnlyAlertOnce(true)
+                .build();
         mNm.notify(mTag3, mId3, n3);
         mFlag3 = Notification.FLAG_ONLY_ALERT_ONCE | Notification.FLAG_AUTO_CANCEL;
     }
 
-    /**
-     * Return to the state machine to progress through the tests.
-     */
-    protected void next() {
-        mHandler.removeCallbacks(mRunner);
-        mHandler.post(mRunner);
-    }
-
-    /**
-     * Wait for things to settle before returning to the state machine.
-     */
-    protected void delay() {
-        delay(2000);
-    }
-
-    /**
-     * Wait for some time.
-     */
-    protected void delay(long waitTime) {
-        mHandler.removeCallbacks(mRunner);
-        mHandler.postDelayed(mRunner, waitTime);
-    }
-
-    protected boolean checkEquals(long expected, long actual, String message) {
-        if (expected == actual) {
-            return true;
-        }
-        logWithStack(String.format(message, expected, actual));
-        return false;
-    }
-
-    protected boolean checkEquals(String expected, String actual, String message) {
-        if (expected.equals(actual)) {
-            return true;
-        }
-        logWithStack(String.format(message, expected, actual));
-        return false;
-    }
-
-    protected boolean checkFlagSet(int expected, int actual, String message) {
-        if ((expected & actual) != 0) {
-            return true;
-        }
-        logWithStack(String.format(message, expected, actual));
-        return false;
-    };
-
-    protected void logWithStack(String message) {
-        Throwable stackTrace = new Throwable();
-        stackTrace.fillInStackTrace();
-        Log.e(TAG, message, stackTrace);
-    }
-
     // Tests
 
-    private void testIsEnabled(int i) {
-        // no setup required
-        Intent settings = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
-        if (settings.resolveActivity(mPackageManager) == null) {
-            logWithStack("failed testIsEnabled: no settings activity");
-            mStatus[i] = FAIL;
-        } else {
-            // TODO: find out why Secure.ENABLED_NOTIFICATION_LISTENERS is hidden
-            String listeners = Secure.getString(getContentResolver(),
-                    "enabled_notification_listeners");
-            if (listeners != null && listeners.contains(LISTENER_PATH)) {
-                mStatus[i] = PASS;
-            } else {
-                mStatus[i] = WAIT_FOR_USER;
-            }
-        }
-        next();
-    }
+    private class NotificationRecievedTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.nls_note_received);
 
-    private void testIsStarted(final int i) {
-        if (mStatus[i] == SETUP) {
-            mStatus[i] = READY;
-            // wait for the service to start
-            delay();
-        } else {
-            MockListener.probeListenerStatus(mContext,
-                    new MockListener.StatusCatcher() {
-                @Override
-                public void accept(int result) {
-                    if (result == Activity.RESULT_OK) {
-                        mStatus[i] = PASS;
-                    } else {
-                        logWithStack("failed testIsStarted: " + result);
-                        mStatus[i] = FAIL;
-                    }
-                    next();
-                }
-            });
         }
-    }
 
-    private void testNotificationRecieved(final int i) {
-        if (mStatus[i] == SETUP) {
-            MockListener.resetListenerData(this);
-            mStatus[i] = CLEARED;
-            // wait for intent to move through the system
-            delay();
-        } else if (mStatus[i] == CLEARED) {
+        @Override
+        void setUp() {
             sendNotifications();
-            mStatus[i] = READY;
+            status = READY;
             // wait for notifications to move through the system
             delay();
-        } else {
+        }
+
+        @Override
+        void test() {
             MockListener.probeListenerPosted(mContext,
                     new MockListener.StringListResultCatcher() {
-                @Override
-                public void accept(List<String> result) {
-                    if (result != null && result.size() > 0 && result.contains(mTag1)) {
-                        mStatus[i] = PASS;
-                    } else {
-                        logWithStack("failed testNotificationRecieved");
-                        mStatus[i] = FAIL;
-                    }
-                    next();
-                }});
-        }
-    }
-
-    private void testDataIntact(final int i) {
-        // no setup required
-        MockListener.probeListenerPayloads(mContext,
-                new MockListener.StringListResultCatcher() {
-            @Override
-            public void accept(List<String> result) {
-                boolean pass = false;
-                Set<String> found = new HashSet<String>();
-                if (result != null && result.size() > 0) {
-                    pass = true;
-                    for(String payloadData : result) {
-                        try {
-                            JSONObject payload = new JSONObject(payloadData);
-                            pass &= checkEquals(mPackageString, payload.getString(JSON_PACKAGE),
-                                    "data integrity test fail: notification package (%s, %s)");
-                            String tag = payload.getString(JSON_TAG);
-                            if (mTag1.equals(tag)) {
-                                found.add(mTag1);
-                                pass &= checkEquals(mIcon1, payload.getInt(JSON_ICON),
-                                        "data integrity test fail: notification icon (%d, %d)");
-                                pass &= checkFlagSet(mFlag1, payload.getInt(JSON_FLAGS),
-                                        "data integrity test fail: notification flags (%d, %d)");
-                                pass &= checkEquals(mId1, payload.getInt(JSON_ID),
-                                        "data integrity test fail: notification ID (%d, %d)");
-                                pass &= checkEquals(mWhen1, payload.getLong(JSON_WHEN),
-                                        "data integrity test fail: notification when (%d, %d)");
-                            } else if (mTag2.equals(tag)) {
-                                found.add(mTag2);
-                                pass &= checkEquals(mIcon2, payload.getInt(JSON_ICON),
-                                        "data integrity test fail: notification icon (%d, %d)");
-                                pass &= checkFlagSet(mFlag2, payload.getInt(JSON_FLAGS),
-                                        "data integrity test fail: notification flags (%d, %d)");
-                                pass &= checkEquals(mId2, payload.getInt(JSON_ID),
-                                        "data integrity test fail: notification ID (%d, %d)");
-                                pass &= checkEquals(mWhen2, payload.getLong(JSON_WHEN),
-                                        "data integrity test fail: notification when (%d, %d)");
-                            } else if (mTag3.equals(tag)) {
-                                found.add(mTag3);
-                                pass &= checkEquals(mIcon3, payload.getInt(JSON_ICON),
-                                        "data integrity test fail: notification icon (%d, %d)");
-                                pass &= checkFlagSet(mFlag3, payload.getInt(JSON_FLAGS),
-                                        "data integrity test fail: notification flags (%d, %d)");
-                                pass &= checkEquals(mId3, payload.getInt(JSON_ID),
-                                        "data integrity test fail: notification ID (%d, %d)");
-                                pass &= checkEquals(mWhen3, payload.getLong(JSON_WHEN),
-                                        "data integrity test fail: notification when (%d, %d)");
+                        @Override
+                        public void accept(List<String> result) {
+                            if (result != null && result.size() > 0 && result.contains(mTag1)) {
+                                status = PASS;
                             } else {
-                                pass = false;
-                                logWithStack("failed on unexpected notification tag: " + tag);
+                                logFail();
+                                status = FAIL;
                             }
-                        } catch (JSONException e) {
-                            pass = false;
-                            Log.e(TAG, "failed to unpack data from mocklistener", e);
-                        }
-                    }
-                }
-                pass &= found.size() == 3;
-                mStatus[i] = pass ? PASS : FAIL;
-                next();
-            }});
-    }
-
-    private void testDismissOne(final int i) {
-        if (mStatus[i] == SETUP) {
-            MockListener.resetListenerData(this);
-            mStatus[i] = CLEARED;
-            // wait for intent to move through the system
-            delay();
-        } else if (mStatus[i] == CLEARED) {
-            MockListener.clearOne(mContext, mTag1, NOTIFICATION_ID + 1);
-            mStatus[i] = READY;
-            delay();
-        } else {
-            MockListener.probeListenerRemoved(mContext,
-                    new MockListener.StringListResultCatcher() {
-                @Override
-                public void accept(List<String> result) {
-                    if (result != null && result.size() > 0 && result.contains(mTag1)) {
-                        mStatus[i] = PASS;
-                        next();
-                    } else {
-                        if (mStatus[i] == RETRY) {
-                            logWithStack("failed testDismissOne");
-                            mStatus[i] = FAIL;
                             next();
-                        } else {
-                            logWithStack("failed testDismissOne, once: retrying");
-                            mStatus[i] = RETRY;
-                            delay();
                         }
-                    }
-                }});
+                    });
+            delay();  // in case the catcher never returns
         }
     }
 
-    private void testDismissAll(final int i) {
-        if (mStatus[i] == SETUP) {
-            MockListener.resetListenerData(this);
-            mStatus[i] = CLEARED;
-            // wait for intent to move through the system
-            delay();
-        } else if (mStatus[i] == CLEARED) {
-            MockListener.clearAll(mContext);
-            mStatus[i] = READY;
-            delay();
-        } else {
-            MockListener.probeListenerRemoved(mContext,
+    private class DataIntactTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.nls_payload_intact);
+        }
+
+        @Override
+        void test() {
+            MockListener.probeListenerPayloads(mContext,
                     new MockListener.StringListResultCatcher() {
-                @Override
-                public void accept(List<String> result) {
-                    if (result != null && result.size() == 2
-                            && result.contains(mTag2) && result.contains(mTag3)) {
-                        mStatus[i] = PASS;
-                        next();
-                    } else {
-                        if (mStatus[i] == RETRY) {
-                            logWithStack("failed testDismissAll");
-                            mStatus[i] = FAIL;
+                        @Override
+                        public void accept(List<String> result) {
+                            Set<String> found = new HashSet<String>();
+                            if (result == null || result.size() == 0) {
+                                status = FAIL;
+                                return;
+                            }
+                            boolean pass = true;
+                            for (String payloadData : result) {
+                                try {
+                                    JSONObject payload = new JSONObject(payloadData);
+                                    pass &= checkEquals(mPackageString,
+                                            payload.getString(JSON_PACKAGE),
+                                            "data integrity test: notification package (%s, %s)");
+                                    String tag = payload.getString(JSON_TAG);
+                                    if (mTag1.equals(tag)) {
+                                        found.add(mTag1);
+                                        pass &= checkEquals(mIcon1, payload.getInt(JSON_ICON),
+                                                "data integrity test: notification icon (%d, %d)");
+                                        pass &= checkFlagSet(mFlag1, payload.getInt(JSON_FLAGS),
+                                                "data integrity test: notification flags (%d, %d)");
+                                        pass &= checkEquals(mId1, payload.getInt(JSON_ID),
+                                                "data integrity test: notification ID (%d, %d)");
+                                        pass &= checkEquals(mWhen1, payload.getLong(JSON_WHEN),
+                                                "data integrity test: notification when (%d, %d)");
+                                    } else if (mTag2.equals(tag)) {
+                                        found.add(mTag2);
+                                        pass &= checkEquals(mIcon2, payload.getInt(JSON_ICON),
+                                                "data integrity test: notification icon (%d, %d)");
+                                        pass &= checkFlagSet(mFlag2, payload.getInt(JSON_FLAGS),
+                                                "data integrity test: notification flags (%d, %d)");
+                                        pass &= checkEquals(mId2, payload.getInt(JSON_ID),
+                                                "data integrity test: notification ID (%d, %d)");
+                                        pass &= checkEquals(mWhen2, payload.getLong(JSON_WHEN),
+                                                "data integrity test: notification when (%d, %d)");
+                                    } else if (mTag3.equals(tag)) {
+                                        found.add(mTag3);
+                                        pass &= checkEquals(mIcon3, payload.getInt(JSON_ICON),
+                                                "data integrity test: notification icon (%d, %d)");
+                                        pass &= checkFlagSet(mFlag3, payload.getInt(JSON_FLAGS),
+                                                "data integrity test: notification flags (%d, %d)");
+                                        pass &= checkEquals(mId3, payload.getInt(JSON_ID),
+                                                "data integrity test: notification ID (%d, %d)");
+                                        pass &= checkEquals(mWhen3, payload.getLong(JSON_WHEN),
+                                                "data integrity test: notification when (%d, %d)");
+                                    } else {
+                                        pass = false;
+                                        logFail("unexpected notification tag: " + tag);
+                                    }
+                                } catch (JSONException e) {
+                                    pass = false;
+                                    Log.e(TAG, "failed to unpack data from mocklistener", e);
+                                }
+                            }
+
+                            pass &= found.size() == 3;
+                            status = pass ? PASS : FAIL;
                             next();
-                        } else {
-                            logWithStack("failed testDismissAll, once: retrying");
-                            mStatus[i] = RETRY;
-                            delay();
                         }
-                    }
-                }
-            });
+                    });
+            delay();  // in case the catcher never returns
+        }
+
+        @Override
+        void tearDown() {
+            mNm.cancelAll();
+            MockListener.resetListenerData(mContext);
+            delay();
         }
     }
 
-    private void testIsDisabled(int i) {
-        // no setup required
-        // TODO: find out why Secure.ENABLED_NOTIFICATION_LISTENERS is hidden
-        String listeners = Secure.getString(getContentResolver(),
-                "enabled_notification_listeners");
-        if (listeners == null || !listeners.contains(LISTENER_PATH)) {
-            mStatus[i] = PASS;
+    private class DismissOneTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.nls_clear_one);
+        }
+
+        @Override
+        void setUp() {
+            sendNotifications();
+            status = READY;
+            delay();
+        }
+
+        @Override
+        void test() {
+            if (status == READY) {
+                MockListener.clearOne(mContext, mTag1, mId1);
+                status = RETEST;
+            } else {
+                MockListener.probeListenerRemoved(mContext,
+                        new MockListener.StringListResultCatcher() {
+                            @Override
+                            public void accept(List<String> result) {
+                                if (result != null && result.size() != 0
+                                        && result.contains(mTag1)
+                                        && !result.contains(mTag2)
+                                        && !result.contains(mTag3)) {
+                                    status = PASS;
+                                } else {
+                                    logFail();
+                                    status = FAIL;
+                                }
+                                next();
+                            }
+                        });
+            }
+            delay();
+        }
+
+        @Override
+        void tearDown() {
+            mNm.cancelAll();
+            MockListener.resetListenerData(mContext);
+            delay();
+        }
+    }
+
+    private class DismissAllTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.nls_clear_all);
+        }
+
+        @Override
+        void setUp() {
+            sendNotifications();
+            status = READY;
+            delay();
+        }
+
+        @Override
+        void test() {
+            if (status == READY) {
+                MockListener.clearAll(mContext);
+                status = RETEST;
+            } else {
+                MockListener.probeListenerRemoved(mContext,
+                        new MockListener.StringListResultCatcher() {
+                            @Override
+                            public void accept(List<String> result) {
+                                if (result != null && result.size() != 0
+                                        && result.contains(mTag1)
+                                        && result.contains(mTag2)
+                                        && result.contains(mTag3)) {
+                                    status = PASS;
+                                } else {
+                                    logFail();
+                                    status = FAIL;
+                                }
+                                next();
+                            }
+                        });
+            }
+            delay();  // in case the catcher never returns
+        }
+
+        @Override
+        void tearDown() {
+            mNm.cancelAll();
+            MockListener.resetListenerData(mContext);
+            delay();
+        }
+    }
+
+    private class IsDisabledTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createNlsSettingsItem(parent, R.string.nls_disable_service);
+        }
+
+        @Override
+        boolean autoStart() {
+            return true;
+        }
+
+        @Override
+        void test() {
+            String listeners = Secure.getString(getContentResolver(),
+                    ENABLED_NOTIFICATION_LISTENERS);
+            if (listeners == null || !listeners.contains(LISTENER_PATH)) {
+                status = PASS;
+            } else {
+                status = WAIT_FOR_USER;
+            }
             next();
-        } else {
-            mStatus[i] = WAIT_FOR_USER;
+        }
+
+        @Override
+        void tearDown() {
+            MockListener.resetListenerData(mContext);
             delay();
         }
     }
 
-    private void testIsStopped(final int i) {
-        if (mStatus[i] == SETUP) {
-            mStatus[i] = READY;
-            // wait for the service to start
-            delay();
-        } else {
+    private class ServiceStoppedTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.nls_service_stopped);
+        }
+
+        @Override
+        void test() {
             MockListener.probeListenerStatus(mContext,
                     new MockListener.StatusCatcher() {
-                @Override
-                public void accept(int result) {
-                    if (result == Activity.RESULT_OK) {
-                        logWithStack("failed testIsStopped");
-                        mStatus[i] = FAIL;
-                    } else {
-                        mStatus[i] = PASS;
-                    }
-                    next();
-                }
-            });
+                        @Override
+                        public void accept(int result) {
+                            if (result == Activity.RESULT_OK) {
+                                logFail();
+                                status = FAIL;
+                            } else {
+                                status = PASS;
+                            }
+                            next();
+                        }
+                    });
+            delay();  // in case the catcher never returns
+        }
+
+        @Override
+        void tearDown() {
+            // wait for intent to move through the system
+            delay();
         }
     }
 
-    private void testNotificationNotRecieved(final int i) {
-        if (mStatus[i] == SETUP) {
-            MockListener.resetListenerData(this);
-            mStatus[i] = CLEARED;
-            // wait for intent to move through the system
-            delay();
-        } else if (mStatus[i] == CLEARED) {
-            // setup for testNotificationRecieved
+    private class NotificationNotReceivedTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.nls_note_missed);
+
+        }
+
+        @Override
+        void setUp() {
             sendNotifications();
-            mStatus[i] = READY;
+            status = READY;
             delay();
-        } else {
+        }
+
+        @Override
+        void test() {
             MockListener.probeListenerPosted(mContext,
                     new MockListener.StringListResultCatcher() {
-                @Override
-                public void accept(List<String> result) {
-                    if (result == null || result.size() == 0) {
-                        mStatus[i] = PASS;
-                    } else {
-                        logWithStack("failed testNotificationNotRecieved");
-                        mStatus[i] = FAIL;
-                    }
-                    next();
-                }});
+                        @Override
+                        public void accept(List<String> result) {
+                            if (result == null || result.size() == 0) {
+                                status = PASS;
+                            } else {
+                                logFail();
+                                status = FAIL;
+                            }
+                            next();
+                        }
+                    });
+            delay();  // in case the catcher never returns
+        }
+
+        @Override
+        void tearDown() {
+            mNm.cancelAll();
+            MockListener.resetListenerData(mContext);
+            delay();
         }
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/PackagePriorityVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/PackagePriorityVerifierActivity.java
new file mode 100644
index 0000000..a6affb3
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/PackagePriorityVerifierActivity.java
@@ -0,0 +1,258 @@
+/*
+ * 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 com.android.cts.verifier.notifications;
+
+import android.app.Notification;
+import android.content.Intent;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import com.android.cts.verifier.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests that the notification ranker honors user preferences about package priority.
+ * Users can, in Settings, specify a package as being high priority. This should
+ * result in the notificaitons from that package being ranked higher than those from
+ * other packages.
+ */
+public class PackagePriorityVerifierActivity
+        extends InteractiveVerifierActivity {
+    private static final String ACTION_POST = "com.android.cts.robot.ACTION_POST";
+    private static final String ACTION_CANCEL = "com.android.cts.robot.ACTION_CANCEL";
+    private static final String EXTRA_ID = "ID";
+    private static final String EXTRA_NOTIFICATION = "NOTIFICATION";
+    private static final String NOTIFICATION_BOT_PACKAGE = "com.android.cts.robot";
+    private CharSequence mAppLabel;
+
+    @Override
+    int getTitleResource() {
+        return R.string.package_priority_test;
+    }
+
+    @Override
+    int getInstructionsResource() {
+        return R.string.package_priority_info;
+    }
+
+    // Test Setup
+
+    @Override
+    protected List<InteractiveTestCase> createTestItems() {
+        mAppLabel = getString(R.string.app_name);
+        List<InteractiveTestCase> tests = new ArrayList<>(17);
+        tests.add(new IsEnabledTest());
+        tests.add(new ServiceStartedTest());
+        tests.add(new WaitForSetPriorityDefault());
+        tests.add(new DefaultOrderTest());
+        tests.add(new WaitForSetPriorityHigh());
+        tests.add(new PackagePriorityOrderTest());
+        return tests;
+    }
+
+    // Tests
+
+    /** Wait for the user to set the target package priority to default. */
+    protected class WaitForSetPriorityDefault extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createRetryItem(parent, R.string.package_priority_default, mAppLabel);
+        }
+
+        @Override
+        void setUp() {
+            Log.i("WaitForSetPriorityDefault", "waiting for user");
+            status = WAIT_FOR_USER;
+        }
+
+        @Override
+        void test() {
+            status = PASS;
+            next();
+        }
+
+        @Override
+        void tearDown() {
+            MockListener.resetListenerData(mContext);
+            delay();
+        }
+    }
+
+    /** Wait for the user to set the target package priority to high. */
+    protected class WaitForSetPriorityHigh extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createRetryItem(parent, R.string.package_priority_high, mAppLabel);
+        }
+
+        @Override
+        void setUp() {
+            Log.i("WaitForSetPriorityHigh", "waiting for user");
+            status = WAIT_FOR_USER;
+        }
+
+        @Override
+        void test() {
+            status = PASS;
+            next();
+        }
+
+        @Override
+        void tearDown() {
+            MockListener.resetListenerData(mContext);
+            delay();
+        }
+    }
+
+    /**
+     * With default priority, the notifcations should be reverse-ordered by time.
+     * A is before B, and therefor should B should rank before A.
+     */
+    protected class DefaultOrderTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.attention_default_order);
+        }
+
+        @Override
+        void setUp() {
+            sendNotifications();
+            status = READY;
+            // wait for notifications to move through the system
+            delay();
+        }
+
+        @Override
+        void test() {
+            MockListener.probeListenerOrder(mContext,
+                    new MockListener.StringListResultCatcher() {
+                        @Override
+                        public void accept(List<String> orderedKeys) {
+                            int rankA = indexOfPackageInKeys(orderedKeys, getPackageName());
+                            int rankB = indexOfPackageInKeys(orderedKeys, NOTIFICATION_BOT_PACKAGE);
+                            if (rankB != -1 && rankB < rankA) {
+                                status = PASS;
+                            } else {
+                                logFail("expected rankA (" + rankA + ") > rankB (" + rankB + ")");
+                                status = FAIL;
+                            }
+                            next();
+                        }
+                    });
+            delay();  // in case the catcher never returns
+        }
+
+        @Override
+        void tearDown() {
+            cancelNotifications();
+            MockListener.resetListenerData(mContext);
+            delay();
+        }
+    }
+
+    /**
+     * With higher package priority, A should rank above B.
+     */
+    protected class PackagePriorityOrderTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.package_priority_user_order);
+        }
+
+        @Override
+        void setUp() {
+            sendNotifications();
+            status = READY;
+            // wait for notifications to move through the system
+            delay();
+        }
+
+        @Override
+        void test() {
+            MockListener.probeListenerOrder(mContext,
+                    new MockListener.StringListResultCatcher() {
+                        @Override
+                        public void accept(List<String> orderedKeys) {
+                            int rankA = indexOfPackageInKeys(orderedKeys, getPackageName());
+                            int rankB = indexOfPackageInKeys(orderedKeys, NOTIFICATION_BOT_PACKAGE);
+                            if (rankA != -1 && rankA < rankB) {
+                                status = PASS;
+                            } else {
+                                logFail("expected rankA (" + rankA + ") < rankB (" + rankB + ")");
+                                status = FAIL;
+                            }
+                            next();
+                        }
+                    });
+            delay();  // in case the catcher never returns
+        }
+
+        @Override
+        void tearDown() {
+            cancelNotifications();
+            MockListener.resetListenerData(mContext);
+            delay();
+        }
+    }
+    // Utilities
+
+    private void sendNotifications() {
+        // post ours first, with an explicit time in the past to avoid any races.
+        Notification.Builder alice = new Notification.Builder(mContext)
+                .setContentTitle("alice title")
+                .setContentText("alice content")
+                .setSmallIcon(R.drawable.ic_stat_alice)
+                .setWhen(System.currentTimeMillis() - 10000L)
+                .setPriority(Notification.PRIORITY_DEFAULT);
+        mNm.notify(0, alice.build());
+
+        // then post theirs, so it should be higher by default due to recency
+        Notification.Builder bob = new Notification.Builder(mContext)
+                .setContentTitle("bob title")
+                .setContentText("bob content")
+                .setSmallIcon(android.R.drawable.stat_notify_error) // must be global resource
+                .setWhen(System.currentTimeMillis())
+                .setPriority(Notification.PRIORITY_DEFAULT);
+        Intent postIntent = new Intent(ACTION_POST);
+        postIntent.setPackage(NOTIFICATION_BOT_PACKAGE);
+        postIntent.putExtra(EXTRA_ID, 0);
+        postIntent.putExtra(EXTRA_NOTIFICATION, bob.build());
+        sendBroadcast(postIntent);
+    }
+
+    private void cancelNotifications() {
+        //cancel ours
+        mNm.cancelAll();
+        //cancel theirs
+        Intent cancelIntent = new Intent(ACTION_CANCEL);
+        cancelIntent.setPackage(NOTIFICATION_BOT_PACKAGE);
+        cancelIntent.putExtra(EXTRA_ID, 0);
+        sendBroadcast(cancelIntent);
+    }
+
+    /** Search a list of notification keys for a given packageName. */
+    private int indexOfPackageInKeys(List<String> orderedKeys, String packageName) {
+        for (int i = 0; i < orderedKeys.size(); i++) {
+            if (orderedKeys.get(i).contains(packageName)) {
+                return i;
+            }
+        }
+        return -1;
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/os/TimeoutResetActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/os/TimeoutResetActivity.java
new file mode 100644
index 0000000..be78556
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/os/TimeoutResetActivity.java
@@ -0,0 +1,112 @@
+/*
+ * 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 com.android.cts.verifier.os;
+
+import android.app.Activity;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.WindowManager;
+
+import java.lang.reflect.Field;
+
+/**
+ * Activity resets the screen timeout to its original timeout. Used devices without Device Admin.
+ */
+public class TimeoutResetActivity extends Activity {
+    public static final String EXTRA_OLD_TIMEOUT = "com.android.cts.verifier.extra.OLD_TIMEOUT";
+    /** Set the timeout to the default to reset the activity to if not specified. */
+    public static final long FALLBACK_TIMEOUT = -1L;
+    /**
+     * Empirically determined buffer time in milliseconds between setting short timeout time and
+     * resetting the timeout.
+     */
+    public static final long RESET_BUFFER_TIME = 2000L;
+    /** Short timeout to trigger screen off. */
+    public static final long SCREEN_OFF_TIMEOUT = 0L;
+    public static final String TAG = TimeoutResetActivity.class.getSimpleName();
+
+    private static long getUserActivityTimeout(WindowManager.LayoutParams params) {
+        try {
+            return getUserActivityTimeoutField(params).getLong(params);
+        } catch (Exception e) {
+            Log.e(TAG, "error loading the userActivityTimeout field", e);
+            return -1;
+        }
+    }
+
+    private static Field getUserActivityTimeoutField(WindowManager.LayoutParams params)
+            throws NoSuchFieldException {
+        return params.getClass().getField("userActivityTimeout");
+    }
+
+    private static void setUserActivityTimeout(WindowManager.LayoutParams params, long timeout) {
+        try {
+            getUserActivityTimeoutField(params).setLong(params, timeout);
+            Log.d(TAG, "UserActivityTimeout set to " + timeout);
+        } catch (Exception e) {
+            Log.e(TAG, "error setting the userActivityTimeout field", e);
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static void turnOffScreen(final Activity activity) {
+        activity.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                WindowManager.LayoutParams params = activity.getWindow().getAttributes();
+
+                // to restore timeout after shutoff
+                final long oldTimeout = getUserActivityTimeout(params);
+
+                final long timeout = SCREEN_OFF_TIMEOUT;
+                setUserActivityTimeout(params, timeout);
+
+                // upon setting this, timeout will be reduced
+                activity.getWindow().setAttributes(params);
+
+                ((AlarmManager) activity.getSystemService(ALARM_SERVICE)).setExact(
+                        AlarmManager.RTC,
+                        System.currentTimeMillis() + RESET_BUFFER_TIME,
+                        PendingIntent.getActivity(
+                                activity.getApplicationContext(),
+                                0,
+                                new Intent(activity, TimeoutResetActivity.class)
+                                        .putExtra(EXTRA_OLD_TIMEOUT, oldTimeout),
+                                0));
+            }
+        });
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        long timeout = getIntent().getLongExtra(EXTRA_OLD_TIMEOUT, FALLBACK_TIMEOUT);
+        if (timeout < 1000) { // in case the old timeout was super low by accident
+            timeout = FALLBACK_TIMEOUT;
+        }
+
+        WindowManager.LayoutParams params = getWindow().getAttributes();
+        setUserActivityTimeout(params, timeout);
+        getWindow().setAttributes(params);
+
+        finish();
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/projection/ProjectionActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/projection/ProjectionActivity.java
index 18d9d43..77de71d 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/projection/ProjectionActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/projection/ProjectionActivity.java
@@ -150,6 +150,8 @@
     public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
         Log.i(TAG, "onSurfaceTextureSizeChanged " + surface.toString() + "w: " + width + " h: "
                 + height);
+        mWidth = width;
+        mHeight = height;
     }
 
     @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/projection/list/ListPresentation.java b/apps/CtsVerifier/src/com/android/cts/verifier/projection/list/ListPresentation.java
index 5dddf5c..46abaaa 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/projection/list/ListPresentation.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/projection/list/ListPresentation.java
@@ -50,7 +50,7 @@
         setContentView(view);
 
         for (int i = 0; i < NUM_ITEMS; ++i) {
-            mItemList.add("Item #" + 1 + i);
+            mItemList.add("Item #" + (1 + i));
         }
 
         ListView listView = (ListView) view.findViewById(R.id.pla_list);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/projection/offscreen/ProjectionOffscreenActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/projection/offscreen/ProjectionOffscreenActivity.java
index 510a03b..cfa097b 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/projection/offscreen/ProjectionOffscreenActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/projection/offscreen/ProjectionOffscreenActivity.java
@@ -35,6 +35,7 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.os.Vibrator;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.KeyEvent;
@@ -75,7 +76,7 @@
         public void run() {
             try {
                 mService.onKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_DOWN));
-                mTimeKeyEventSent = SystemClock.uptimeMillis();
+                mTimeKeyEventSent = SystemClock.elapsedRealtime();
             } catch (RemoteException e) {
                 Log.e(TAG, "Error running onKeyEvent", e);
             }
@@ -89,6 +90,7 @@
             Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
             Ringtone r = RingtoneManager.getRingtone(getApplicationContext(), notification);
             r.play();
+            ((Vibrator) getSystemService(VIBRATOR_SERVICE)).vibrate(1000);
         }
     };
 
@@ -101,11 +103,11 @@
             handler.postDelayed(
                     sendKeyEventRunnable, DELAYED_RUNNABLE_TIME);
             mStatusView.setText("Running test...");
-            mTimeScreenTurnedOff = SystemClock.uptimeMillis();
+            mTimeScreenTurnedOff = SystemClock.elapsedRealtime();
             // Notify user its safe to turn screen back on after 5s + fudge factor
             handler.postDelayed(playNotificationRunnable, TIME_SCREEN_OFF + 500);
         } else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
-            if (SystemClock.uptimeMillis() - mTimeScreenTurnedOff < TIME_SCREEN_OFF) {
+            if (SystemClock.elapsedRealtime() - mTimeScreenTurnedOff < TIME_SCREEN_OFF) {
                 mStatusView.setText("ERROR: Turned on screen too early");
                 getPassButton().setEnabled(false);
                 mTestStatus = TestStatus.FAILED;
@@ -196,7 +198,7 @@
 
         if (mTimeKeyEventSent != 0
                 && mTestStatus == TestStatus.RUNNING
-                && mTimeKeyEventSent + RENDERER_DELAY_THRESHOLD < SystemClock.uptimeMillis()) {
+                && mTimeKeyEventSent + RENDERER_DELAY_THRESHOLD < SystemClock.elapsedRealtime()) {
             mTestStatus = TestStatus.FAILED;
             mStatusView.setText("Failed: took too long to render");
         }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sample/SampleTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sample/SampleTestActivity.java
index 25f90d9..41bc303 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sample/SampleTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sample/SampleTestActivity.java
@@ -16,8 +16,12 @@
 
 package com.android.cts.verifier.sample;
 
+import com.android.compatibility.common.util.ReportLog;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
 import com.android.cts.verifier.PassFailButtons;
 import com.android.cts.verifier.R;
+import com.android.cts.verifier.TestResult;
 
 import android.content.Intent;
 import android.net.Uri;
@@ -61,6 +65,7 @@
             public void onClick(View v) {
                 try {
                     createFileAndShare();
+                    recordMetricsExample();
                 } catch (Exception e) {
                     e.printStackTrace();
                 }
@@ -68,6 +73,21 @@
         });
     }
 
+    private void recordMetricsExample() {
+        double[] metricValues = new double[] {1, 11, 21, 1211, 111221};
+
+        // Record metric results
+        getReportLog().setSummary(
+                "Sample Summary", 1.0, ResultType.HIGHER_BETTER, ResultUnit.BYTE);
+        getReportLog().addValues("Sample Values", metricValues, ResultType.NEUTRAL, ResultUnit.FPS);
+
+        // Alternatively, activities can invoke TestResult directly to record metrics
+        ReportLog reportLog = new PassFailButtons.CtsVerifierReportLog();
+        reportLog.setSummary("Sample Summary", 1.0, ResultType.HIGHER_BETTER, ResultUnit.BYTE);
+        getReportLog().addValues("Sample Values", metricValues, ResultType.NEUTRAL, ResultUnit.FPS);
+        TestResult.setPassedResult(this, "manualSample", "manualDetails", reportLog);
+    }
+
     /**
      * Creates a temporary file containing the test string and then issues the intent to share it.
      *
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/screenpinning/ScreenPinningTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/screenpinning/ScreenPinningTestActivity.java
new file mode 100644
index 0000000..200865e
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/screenpinning/ScreenPinningTestActivity.java
@@ -0,0 +1,242 @@
+/*
+ * 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.cts.verifier.screenpinning;
+
+import android.app.ActivityManager;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+public class ScreenPinningTestActivity extends PassFailButtons.Activity {
+
+    private static final String TAG = "ScreenPinningTestActivity";
+    private static final String KEY_CURRENT_TEST = "keyCurrentTest";
+
+    private Test[] mTests;
+    private int mTestIndex;
+
+    private ActivityManager mActivityManager;
+    private Button mNextButton;
+    private LinearLayout mInstructions;
+
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.screen_pinning);
+        setPassFailButtonClickListeners();
+
+        mTests = new Test[] {
+            // Verify not already pinned.
+            mCheckStartedUnpinned,
+            // Enter pinning, verify pinned, try leaving and have the user exit.
+            mCheckStartPinning,
+            mCheckIsPinned,
+            mCheckTryLeave,
+            mCheckUnpin,
+            // Enter pinning, verify pinned, and use APIs to exit.
+            mCheckStartPinning,
+            mCheckIsPinned,
+            mCheckUnpinFromCode,
+            // All done.
+            mDone,
+        };
+
+        mActivityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
+        mInstructions = (LinearLayout) findViewById(R.id.instructions_list);
+
+        mNextButton = (Button) findViewById(R.id.next_button);
+        mNextButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if ((mTestIndex >= 0) && (mTestIndex < mTests.length)) {
+                    mTests[mTestIndex].onNextClick();
+                }
+            }
+        });
+
+        // Don't allow pass until all tests complete.
+        findViewById(R.id.pass_button).setVisibility(View.GONE);
+
+        // Figure out if we are in a test or starting from the beginning.
+        if (savedInstanceState != null && savedInstanceState.containsKey(KEY_CURRENT_TEST)) {
+            mTestIndex = savedInstanceState.getInt(KEY_CURRENT_TEST);
+        } else {
+            mTestIndex = 0;
+        }
+        // Display any pre-existing text.
+        for (int i = 0; i < mTestIndex; i++) {
+            mTests[i].showText();
+        }
+        mTests[mTestIndex].run();
+    };
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        outState.putInt(KEY_CURRENT_TEST, mTestIndex);
+    }
+
+    @Override
+    public void onBackPressed() {
+        // Block back button so we can test screen pinning exit functionality.
+        // Users can still leave by pressing fail (or when done the pass) button.
+    }
+
+    private void show(int id) {
+        TextView tv = new TextView(this);
+        tv.setPadding(10, 10, 10, 30);
+        tv.setText(id);
+        mInstructions.addView(tv);
+    }
+
+    private void succeed() {
+        runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mTestIndex++;
+                if (mTestIndex < mTests.length) {
+                    mTests[mTestIndex].run();
+                } else {
+                    mNextButton.setVisibility(View.GONE);
+                    findViewById(R.id.pass_button).setVisibility(View.VISIBLE);
+                }
+            }
+        });
+    }
+
+    private void error(int errorId) {
+        error(errorId, new Throwable());
+    }
+
+    private void error(final int errorId, final Throwable cause) {
+        runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                String error = getString(errorId);
+                Log.d(TAG, error, cause);
+                // No more instructions needed.
+                findViewById(R.id.instructions_group).setVisibility(View.GONE);
+
+                ((TextView) findViewById(R.id.error_text)).setText(error);
+            }
+        });
+    }
+
+    // Verify we don't start in screen pinning.
+    private final Test mCheckStartedUnpinned = new Test(0) {
+        public void run() {
+            if (mActivityManager.isInLockTaskMode()) {
+                error(R.string.error_screen_already_pinned);
+            } else {
+                succeed();
+            }
+        }
+    };
+
+    // Start screen pinning by having the user click next then confirm it for us.
+    private final Test mCheckStartPinning = new Test(R.string.screen_pin_instructions) {
+        protected void onNextClick() {
+            startLockTask();
+            succeed();
+        }
+    };
+
+    // Click next and check that we got pinned.
+    // Wait for the user to click next to verify that they got back from the prompt
+    // successfully.
+    private final Test mCheckIsPinned = new Test(R.string.screen_pin_check_pinned) {
+        protected void onNextClick() {
+            if (mActivityManager.isInLockTaskMode()) {
+                succeed();
+            } else {
+                error(R.string.error_screen_pinning_did_not_start);
+            }
+        }
+    };
+
+    // Tell user to try to leave.
+    private final Test mCheckTryLeave = new Test(R.string.screen_pin_no_exit) {
+        protected void onNextClick() {
+            if (mActivityManager.isInLockTaskMode()) {
+                succeed();
+            } else {
+                error(R.string.error_screen_no_longer_pinned);
+            }
+        }
+    };
+
+    // Verify that the user unpinned and it worked.
+    private final Test mCheckUnpin = new Test(R.string.screen_pin_exit) {
+        protected void onNextClick() {
+            if (!mActivityManager.isInLockTaskMode()) {
+                succeed();
+            } else {
+                error(R.string.error_screen_pinning_did_not_exit);
+            }
+        }
+    };
+
+    // Unpin from code and check that it worked.
+    private final Test mCheckUnpinFromCode = new Test(0) {
+        protected void run() {
+            if (!mActivityManager.isInLockTaskMode()) {
+                error(R.string.error_screen_pinning_did_not_start);
+                return;
+            }
+            stopLockTask();
+            if (!mActivityManager.isInLockTaskMode()) {
+                succeed();
+            } else {
+                error(R.string.error_screen_pinning_couldnt_exit);
+            }
+        };
+    };
+
+    private final Test mDone = new Test(R.string.screen_pinning_done) {
+        protected void run() {
+            showText();
+            succeed();
+        };
+    };
+
+    private abstract class Test {
+        private final int mResId;
+
+        public Test(int showId) {
+            mResId = showId;
+        }
+
+        protected void run() {
+            showText();
+        }
+
+        public void showText() {
+            if (mResId == 0) {
+                return;
+            }
+            show(mResId);
+        }
+
+        protected void onNextClick() {
+        }
+    }
+
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/SensorPowerTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/SensorPowerTestActivity.java
index 8370d3e..74d51e4 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/SensorPowerTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/SensorPowerTestActivity.java
@@ -63,7 +63,7 @@
 
     @Override
     protected void activitySetUp() throws InterruptedException {
-        mScreenManipulator = new SensorTestScreenManipulator(getApplicationContext());
+        mScreenManipulator = new SensorTestScreenManipulator(this);
         mScreenManipulator.initialize(this);
     }
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/base/SensorCtsTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/base/SensorCtsTestActivity.java
index 16c5fcd..6512fd3 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/base/SensorCtsTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/base/SensorCtsTestActivity.java
@@ -69,7 +69,7 @@
     protected void activitySetUp() throws InterruptedException {
         PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
         mWakeLock =  powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "SensorCtsTests");
-        mScreenManipulator = new SensorTestScreenManipulator(getApplicationContext());
+        mScreenManipulator = new SensorTestScreenManipulator(this);
         mScreenManipulator.initialize(this);
 
         SensorTestLogger logger = getTestLogger();
@@ -80,6 +80,7 @@
         // automated CTS tests run with the USB connected, so the AP doesn't go to sleep
         // here we are not connected to USB, so we need to hold a wake-lock to avoid going to sleep
         mWakeLock.acquire();
+
         mScreenManipulator.turnScreenOff();
     }
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/helpers/SensorTestScreenManipulator.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/helpers/SensorTestScreenManipulator.java
index 835ff56..2956ed7 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/helpers/SensorTestScreenManipulator.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/helpers/SensorTestScreenManipulator.java
@@ -16,6 +16,7 @@
 
 package com.android.cts.verifier.sensors.helpers;
 
+import com.android.cts.verifier.os.TimeoutResetActivity;
 import com.android.cts.verifier.sensors.base.BaseSensorTestActivity;
 import com.android.cts.verifier.sensors.base.ISensorTestStateContainer;
 
@@ -27,8 +28,12 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
 import android.os.PowerManager;
 import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.concurrent.CountDownLatch;
 
 /**
  * A class that provides functionality to manipulate the state of the device's screen.
@@ -52,8 +57,9 @@
  * - in a single-threaded environment
  */
 public class SensorTestScreenManipulator {
+    private static final String TAG = SensorTestScreenManipulator.class.getSimpleName();
 
-    private final Context mContext;
+    private final Activity mActivity;
     private final DevicePolicyManager mDevicePolicyManager;
     private final ComponentName mComponentName;
     private final PowerManager.WakeLock mWakeUpScreenWakeLock;
@@ -62,16 +68,17 @@
     private InternalBroadcastReceiver mBroadcastReceiver;
     private boolean mTurnOffScreenOnPowerDisconnected;
 
-    public SensorTestScreenManipulator(Context context) {
-        mContext = context;
-        mComponentName = SensorDeviceAdminReceiver.getComponentName(context);
+
+    public SensorTestScreenManipulator(Activity activity) {
+        mActivity = activity;
+        mComponentName = SensorDeviceAdminReceiver.getComponentName(activity);
         mDevicePolicyManager =
-                (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
+                (DevicePolicyManager) activity.getSystemService(Context.DEVICE_POLICY_SERVICE);
 
         int levelAndFlags = PowerManager.FULL_WAKE_LOCK
                 | PowerManager.ON_AFTER_RELEASE
                 | PowerManager.ACQUIRE_CAUSES_WAKEUP;
-        PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+        PowerManager powerManager = (PowerManager) activity.getSystemService(Context.POWER_SERVICE);
         mWakeUpScreenWakeLock = powerManager.newWakeLock(levelAndFlags, "SensorTestWakeUpScreen");
         mWakeUpScreenWakeLock.setReferenceCounted(false);
         mKeepScreenOnWakeLock = powerManager.newWakeLock(levelAndFlags, "SensorTestKeepScreenOn");
@@ -87,7 +94,7 @@
      */
     public synchronized void initialize(ISensorTestStateContainer stateContainer)
             throws InterruptedException {
-        if (!isDeviceAdminInitialized()) {
+        if (hasDeviceAdminFeature() && !isDeviceAdminInitialized()) {
             Intent intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);
             intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, mComponentName);
             int resultCode = stateContainer.executeActivity(intent);
@@ -101,7 +108,7 @@
             mBroadcastReceiver = new InternalBroadcastReceiver();
             IntentFilter intentFilter = new IntentFilter();
             intentFilter.addAction(Intent.ACTION_POWER_DISCONNECTED);
-            mContext.registerReceiver(mBroadcastReceiver, intentFilter);
+            mActivity.registerReceiver(mBroadcastReceiver, intentFilter);
         }
     }
 
@@ -111,7 +118,7 @@
      */
     public synchronized  void close() {
         if (mBroadcastReceiver != null) {
-            mContext.unregisterReceiver(mBroadcastReceiver);
+            mActivity.unregisterReceiver(mBroadcastReceiver);
             mBroadcastReceiver = null;
         }
     }
@@ -121,8 +128,30 @@
      */
     public synchronized void turnScreenOff() {
         ensureDeviceAdminInitialized();
+
+        final CountDownLatch screenOffSignal = new CountDownLatch(1);
+        BroadcastReceiver screenOffBroadcastReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                mActivity.unregisterReceiver(this);
+                screenOffSignal.countDown();
+            }
+        };
+        mActivity.registerReceiver(
+                screenOffBroadcastReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));
+
         releaseScreenOn();
-        mDevicePolicyManager.lockNow();
+        if (hasDeviceAdminFeature()) {
+            mDevicePolicyManager.lockNow();
+        } else {
+            TimeoutResetActivity.turnOffScreen(mActivity);
+        }
+
+        try {
+            screenOffSignal.await();
+        } catch (InterruptedException e) {
+            Log.wtf(TAG, "error waiting for screen off signal", e);
+        }
     }
 
     /**
@@ -175,7 +204,7 @@
     }
 
     private void ensureDeviceAdminInitialized() throws IllegalStateException {
-        if (!isDeviceAdminInitialized()) {
+        if (hasDeviceAdminFeature() && !isDeviceAdminInitialized()) {
             throw new IllegalStateException("Component must be initialized before it can be used.");
         }
     }
@@ -188,6 +217,10 @@
                 .hasGrantedPolicy(mComponentName, DeviceAdminInfo.USES_POLICY_FORCE_LOCK);
     }
 
+    private boolean hasDeviceAdminFeature() {
+        return mActivity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN);
+    }
+
     private class InternalBroadcastReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/tv/MockTvInputService.java b/apps/CtsVerifier/src/com/android/cts/verifier/tv/MockTvInputService.java
new file mode 100644
index 0000000..f4460de
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/tv/MockTvInputService.java
@@ -0,0 +1,325 @@
+/*
+ * 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 com.android.cts.verifier.tv;
+
+import com.android.cts.verifier.R;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.media.tv.TvContentRating;
+import android.media.tv.TvInputManager;
+import android.media.tv.TvInputService;
+import android.media.tv.TvTrackInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.Surface;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.TextView;
+
+import com.android.cts.verifier.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class MockTvInputService extends TvInputService {
+    private static final String TAG = "MockTvInputService";
+
+    private static final String BROADCAST_ACTION = "action";
+    private static final String SELECT_TRACK_TYPE = "type";
+    private static final String SELECT_TRACK_ID = "id";
+    private static final String CAPTION_ENABLED = "enabled";
+
+    private static Object sLock = new Object();
+    private static Callback sTuneCallback = null;
+    private static Callback sOverlayViewCallback = null;
+    private static Callback sBroadcastCallback = null;
+    private static Callback sUnblockContentCallback = null;
+    private static Callback sSelectTrackCallback = null;
+    private static Callback sSetCaptionEnabledCallback = null;
+    private static TvContentRating sRating = null;
+
+    static final TvTrackInfo sEngAudioTrack =
+            new TvTrackInfo.Builder(TvTrackInfo.TYPE_AUDIO, "audio_eng")
+            .setAudioChannelCount(2)
+            .setAudioSampleRate(48000)
+            .setLanguage("eng")
+            .build();
+    static final TvTrackInfo sSpaAudioTrack =
+            new TvTrackInfo.Builder(TvTrackInfo.TYPE_AUDIO, "audio_spa")
+            .setAudioChannelCount(2)
+            .setAudioSampleRate(48000)
+            .setLanguage("spa")
+            .build();
+    static final TvTrackInfo sEngSubtitleTrack =
+            new TvTrackInfo.Builder(TvTrackInfo.TYPE_SUBTITLE, "subtitle_eng")
+            .setLanguage("eng")
+            .build();
+    static final TvTrackInfo sSpaSubtitleTrack =
+            new TvTrackInfo.Builder(TvTrackInfo.TYPE_SUBTITLE, "subtitle_spa")
+            .setLanguage("spa")
+            .build();
+
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            synchronized (sLock) {
+                if (sBroadcastCallback != null) {
+                    String expectedAction =
+                            sBroadcastCallback.getBundle().getString(BROADCAST_ACTION);
+                    if (intent.getAction().equals(expectedAction)) {
+                        sBroadcastCallback.post();
+                        sBroadcastCallback = null;
+                    }
+                }
+            }
+        }
+    };
+
+    static void expectTune(View postTarget, Runnable successCallback) {
+        synchronized (sLock) {
+            sTuneCallback = new Callback(postTarget, successCallback);
+        }
+    }
+
+    static void expectBroadcast(View postTarget, String action, Runnable successCallback) {
+        synchronized (sLock) {
+            sBroadcastCallback = new Callback(postTarget, successCallback);
+            sBroadcastCallback.getBundle().putString(BROADCAST_ACTION, action);
+        }
+    }
+
+    static void expectUnblockContent(View postTarget, Runnable successCallback) {
+        synchronized (sLock) {
+            sUnblockContentCallback = new Callback(postTarget, successCallback);
+        }
+    }
+
+    static void setBlockRating(TvContentRating rating) {
+        synchronized (sLock) {
+            sRating = rating;
+        }
+    }
+
+    static void expectOverlayView(View postTarget, Runnable successCallback) {
+        synchronized (sLock) {
+            sOverlayViewCallback = new Callback(postTarget, successCallback);
+        }
+    }
+
+    static void expectSelectTrack(int type, String id, View postTarget, Runnable successCallback) {
+        synchronized (sLock) {
+            sSelectTrackCallback = new Callback(postTarget, successCallback);
+            sSelectTrackCallback.getBundle().putInt(SELECT_TRACK_TYPE, type);
+            sSelectTrackCallback.getBundle().putString(SELECT_TRACK_ID, id);
+        }
+    }
+
+    static void expectSetCaptionEnabled(boolean enabled, View postTarget,
+            Runnable successCallback) {
+        synchronized (sLock) {
+            sSetCaptionEnabledCallback = new Callback(postTarget, successCallback);
+            sSetCaptionEnabledCallback.getBundle().putBoolean(CAPTION_ENABLED, enabled);
+        }
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(TvInputManager.ACTION_BLOCKED_RATINGS_CHANGED);
+        intentFilter.addAction(TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED);
+        registerReceiver(mBroadcastReceiver, intentFilter);
+    }
+
+    @Override
+    public void onDestroy() {
+        unregisterReceiver(mBroadcastReceiver);
+        super.onDestroy();
+    }
+
+    @Override
+    public Session onCreateSession(String inputId) {
+        Session session = new MockSessionImpl(this);
+        session.setOverlayViewEnabled(true);
+        return session;
+    }
+
+    private static class MockSessionImpl extends Session {
+        private final Context mContext;
+        private Surface mSurface = null;
+        private List<TvTrackInfo> mTracks = new ArrayList<>();
+
+        private MockSessionImpl(Context context) {
+            super(context);
+            mContext = context;
+            mTracks.add(sEngAudioTrack);
+            mTracks.add(sSpaAudioTrack);
+            mTracks.add(sEngSubtitleTrack);
+            mTracks.add(sSpaSubtitleTrack);
+        }
+
+        @Override
+        public void onRelease() {
+        }
+
+        private void draw() {
+            Surface surface = mSurface;
+            if (surface == null) return;
+            if (!surface.isValid()) return;
+
+            Canvas c = surface.lockCanvas(null);
+            if (c == null) return;
+            try {
+                Bitmap b = BitmapFactory.decodeResource(
+                        mContext.getResources(), R.drawable.icon);
+                int srcWidth = b.getWidth();
+                int srcHeight = b.getHeight();
+                int dstWidth = c.getWidth();
+                int dstHeight = c.getHeight();
+                c.drawColor(Color.BLACK);
+                c.drawBitmap(b, new Rect(0, 0, srcWidth, srcHeight),
+                        new Rect(10, 10, dstWidth - 10, dstHeight - 10), null);
+            } finally {
+                surface.unlockCanvasAndPost(c);
+            }
+        }
+
+        @Override
+        public View onCreateOverlayView() {
+            LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
+                    LAYOUT_INFLATER_SERVICE);
+            View view = inflater.inflate(R.layout.tv_overlay, null);
+            TextView textView = (TextView) view.findViewById(R.id.overlay_view_text);
+            textView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
+                @Override
+                public void onLayoutChange(View v, int left, int top, int right, int bottom,
+                        int oldLeft, int oldTop, int oldRight, int oldBottom) {
+                    Callback overlayViewCallback = null;
+                    synchronized (sLock) {
+                        overlayViewCallback = sOverlayViewCallback;
+                        sOverlayViewCallback = null;
+                    }
+                    if (overlayViewCallback != null) {
+                        overlayViewCallback.post();
+                    }
+                }
+            });
+            return view;
+        }
+
+        @Override
+        public boolean onSetSurface(Surface surface) {
+            mSurface = surface;
+            draw();
+            return true;
+        }
+
+        @Override
+        public void onSetStreamVolume(float volume) {
+        }
+
+        @Override
+        public boolean onTune(Uri channelUri) {
+            synchronized (sLock) {
+                if (sRating != null) {
+                    notifyContentBlocked(sRating);
+                }
+                if (sTuneCallback != null) {
+                    sTuneCallback.post();
+                    sTuneCallback = null;
+                }
+                if (sRating == null) {
+                    notifyContentAllowed();
+                }
+            }
+            notifyVideoAvailable();
+            notifyTracksChanged(mTracks);
+            notifyTrackSelected(TvTrackInfo.TYPE_AUDIO, sEngAudioTrack.getId());
+            notifyTrackSelected(TvTrackInfo.TYPE_SUBTITLE, null);
+            return true;
+        }
+
+        @Override
+        public boolean onSelectTrack(int type, String trackId) {
+            synchronized (sLock) {
+                if (sSelectTrackCallback != null) {
+                    Bundle bundle = sSelectTrackCallback.getBundle();
+                    if (bundle.getInt(SELECT_TRACK_TYPE) == type
+                            && bundle.getString(SELECT_TRACK_ID).equals(trackId)) {
+                        sSelectTrackCallback.post();
+                        sSelectTrackCallback = null;
+                    }
+                }
+            }
+            notifyTrackSelected(type, trackId);
+            return true;
+        }
+
+        @Override
+        public void onSetCaptionEnabled(boolean enabled) {
+            synchronized (sLock) {
+                if (sSetCaptionEnabledCallback != null) {
+                    Bundle bundle = sSetCaptionEnabledCallback.getBundle();
+                    if (bundle.getBoolean(CAPTION_ENABLED) == enabled) {
+                        sSetCaptionEnabledCallback.post();
+                        sSetCaptionEnabledCallback = null;
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void onUnblockContent(TvContentRating unblockedRating) {
+            synchronized (sLock) {
+                if (sRating != null && sRating.equals(unblockedRating)) {
+                    sUnblockContentCallback.post();
+                    sRating = null;
+                    notifyContentAllowed();
+                }
+            }
+        }
+    }
+
+    private static class Callback {
+        private final View mPostTarget;
+        private final Runnable mAction;
+        private final Bundle mBundle = new Bundle();
+
+        Callback(View postTarget, Runnable action) {
+            mPostTarget = postTarget;
+            mAction = action;
+        }
+
+        public void post() {
+            mPostTarget.post(mAction);
+        }
+
+        public Bundle getBundle() {
+            return mBundle;
+        }
+    }
+}
diff --git a/common/util/tests/src/com/android/compatibility/common/util/CommonUtilTest.java b/apps/CtsVerifier/src/com/android/cts/verifier/tv/MockTvInputSettingsActivity.java
similarity index 75%
rename from common/util/tests/src/com/android/compatibility/common/util/CommonUtilTest.java
rename to apps/CtsVerifier/src/com/android/cts/verifier/tv/MockTvInputSettingsActivity.java
index a376373..4231db7 100644
--- a/common/util/tests/src/com/android/compatibility/common/util/CommonUtilTest.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/tv/MockTvInputSettingsActivity.java
@@ -14,12 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.compatibility.common.util;
+package com.android.cts.verifier.tv;
 
-import junit.framework.TestCase;
+import android.preference.PreferenceActivity;
 
-public class CommonUtilTest extends TestCase {
-
-    // TODO(stuartscott): Add tests when there is something to test.
+public class MockTvInputSettingsActivity extends PreferenceActivity {
 
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/tv/MockTvInputSetupActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/tv/MockTvInputSetupActivity.java
new file mode 100644
index 0000000..81a8edc
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/tv/MockTvInputSetupActivity.java
@@ -0,0 +1,82 @@
+/*
+ * 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 com.android.cts.verifier.tv;
+
+import android.app.Activity;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.media.tv.TvContract;
+import android.media.tv.TvInputInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Pair;
+import android.view.View;
+
+public class MockTvInputSetupActivity extends Activity {
+    private static final String TAG = "MockTvInputSetupActivity";
+
+    private static final String CHANNEL_NUMBER = "999-0";
+    private static final String CHANNEL_NAME = "Dummy";
+
+    private static Object sLock = new Object();
+    private static Pair<View, Runnable> sLaunchCallback = null;
+
+    static void expectLaunch(View postTarget, Runnable successCallback) {
+        synchronized (sLock) {
+            sLaunchCallback = Pair.create(postTarget, successCallback);
+        }
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        try {
+            super.onCreate(savedInstanceState);
+            final String inputId = getIntent().getStringExtra(TvInputInfo.EXTRA_INPUT_ID);
+            final Uri uri = TvContract.buildChannelsUriForInput(inputId);
+            final String[] projection = { TvContract.Channels._ID };
+            try (Cursor cursor = getContentResolver().query(uri, projection, null, null, null)) {
+                // If we already have channels, just finish without doing anything.
+                if (cursor != null && cursor.getCount() > 0) {
+                    return;
+                }
+            }
+            ContentValues values = new ContentValues();
+            values.put(TvContract.Channels.COLUMN_INPUT_ID, inputId);
+            values.put(TvContract.Channels.COLUMN_DISPLAY_NUMBER, CHANNEL_NUMBER);
+            values.put(TvContract.Channels.COLUMN_DISPLAY_NAME, CHANNEL_NAME);
+            Uri channelUri = getContentResolver().insert(uri, values);
+            // If the channel's ID happens to be zero, we add another and delete the one.
+            if (ContentUris.parseId(channelUri) == 0) {
+                getContentResolver().insert(uri, values);
+                getContentResolver().delete(channelUri, null, null);
+            }
+        } finally {
+            Pair<View, Runnable> launchCallback = null;
+            synchronized (sLock) {
+                launchCallback = sLaunchCallback;
+                sLaunchCallback = null;
+            }
+            if (launchCallback != null) {
+                launchCallback.first.post(launchCallback.second);
+            }
+
+            setResult(Activity.RESULT_OK);
+            finish();
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/tv/MultipleTracksTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/tv/MultipleTracksTestActivity.java
new file mode 100644
index 0000000..66af4c6
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/tv/MultipleTracksTestActivity.java
@@ -0,0 +1,154 @@
+/*
+ * 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 com.android.cts.verifier.tv;
+
+import com.android.cts.verifier.R;
+
+import android.content.Intent;
+import android.database.Cursor;
+import android.media.tv.TvContentRating;
+import android.media.tv.TvContract;
+import android.media.tv.TvInputManager;
+import android.media.tv.TvTrackInfo;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+/**
+ * Tests for verifying TV app behavior on multiple tracks and subtitle.
+ */
+public class MultipleTracksTestActivity extends TvAppVerifierActivity
+        implements View.OnClickListener {
+    private static final String TAG = "MultipleTracksTestActivity";
+
+    private static final long TIMEOUT_MS = 5l * 60l * 1000l;  // 5 mins.
+
+    private View mSelectSubtitleItem;
+    private View mVerifySetCaptionEnabledItem;
+    private View mVerifySelectSubtitleItem;
+    private View mSelectAudioItem;
+    private View mVerifySelectAudioItem;
+
+    private Intent mTvAppIntent = null;
+
+    @Override
+    public void onClick(View v) {
+        final View postTarget = getPostTarget();
+
+        if (containsButton(mSelectSubtitleItem, v)) {
+            final Runnable failCallback = new Runnable() {
+                @Override
+                public void run() {
+                    setPassState(mVerifySetCaptionEnabledItem, false);
+                    setPassState(mVerifySelectSubtitleItem, false);
+                }
+            };
+            postTarget.postDelayed(failCallback, TIMEOUT_MS);
+            MockTvInputService.expectSetCaptionEnabled(true, postTarget, new Runnable() {
+                @Override
+                public void run() {
+                    postTarget.removeCallbacks(failCallback);
+                    setPassState(mSelectSubtitleItem, true);
+                    setPassState(mVerifySetCaptionEnabledItem, true);
+                    Integer tag = (Integer) mSelectAudioItem.getTag();
+                    if (tag == 0) {
+                        mSelectAudioItem.setTag(Integer.valueOf(1));
+                    } else if (tag == 1) {
+                        setButtonEnabled(mSelectAudioItem, true);
+                    }
+                }
+            });
+            MockTvInputService.expectSelectTrack(TvTrackInfo.TYPE_SUBTITLE,
+                    MockTvInputService.sEngSubtitleTrack.getId(), postTarget, new Runnable() {
+                @Override
+                public void run() {
+                    postTarget.removeCallbacks(failCallback);
+                    setPassState(mSelectSubtitleItem, true);
+                    setPassState(mVerifySelectSubtitleItem, true);
+                    Integer tag = (Integer) mSelectAudioItem.getTag();
+                    if (tag == 0) {
+                        mSelectAudioItem.setTag(Integer.valueOf(1));
+                    } else if (tag == 1) {
+                        setButtonEnabled(mSelectAudioItem, true);
+                    }
+                }
+            });
+        } else if (containsButton(mSelectAudioItem, v)) {
+            final Runnable failCallback = new Runnable() {
+                @Override
+                public void run() {
+                    setPassState(mVerifySelectAudioItem, false);
+                }
+            };
+            postTarget.postDelayed(failCallback, TIMEOUT_MS);
+            MockTvInputService.expectSelectTrack(TvTrackInfo.TYPE_AUDIO,
+                    MockTvInputService.sSpaAudioTrack.getId(), postTarget, new Runnable() {
+                @Override
+                public void run() {
+                    postTarget.removeCallbacks(failCallback);
+                    setPassState(mSelectAudioItem, true);
+                    setPassState(mVerifySelectAudioItem, true);
+                    getPassButton().setEnabled(true);
+                }
+            });
+        }
+        if (mTvAppIntent == null) {
+            String[] projection = { TvContract.Channels._ID };
+            try (Cursor cursor = getContentResolver().query(TvContract.Channels.CONTENT_URI,
+                    projection, null, null, null)) {
+                if (cursor != null && cursor.moveToNext()) {
+                    mTvAppIntent = new Intent(Intent.ACTION_VIEW,
+                            TvContract.buildChannelUri(cursor.getLong(0)));
+                }
+            }
+            if (mTvAppIntent == null) {
+                Toast.makeText(this, R.string.tv_channel_not_found, Toast.LENGTH_SHORT).show();
+                return;
+            }
+        }
+        startActivity(mTvAppIntent);
+    }
+
+    @Override
+    protected void createTestItems() {
+        mSelectSubtitleItem = createUserItem(
+                R.string.tv_multiple_tracks_test_select_subtitle,
+                R.string.tv_launch_tv_app, this);
+        setButtonEnabled(mSelectSubtitleItem, true);
+        mVerifySetCaptionEnabledItem = createAutoItem(
+                R.string.tv_multiple_tracks_test_verify_set_caption_enabled);
+        mVerifySelectSubtitleItem = createAutoItem(
+                R.string.tv_multiple_tracks_test_verify_select_subtitle);
+        mSelectAudioItem = createUserItem(
+                R.string.tv_multiple_tracks_test_select_audio,
+                R.string.tv_launch_tv_app, this);
+        mSelectAudioItem.setTag(Integer.valueOf(0));
+        mVerifySelectAudioItem = createAutoItem(
+                R.string.tv_multiple_tracks_test_verify_select_audio);
+    }
+
+    @Override
+    protected void setInfoResources() {
+        setInfoResources(R.string.tv_multiple_tracks_test,
+                R.string.tv_multiple_tracks_test_info, -1);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/tv/ParentalControlTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/tv/ParentalControlTestActivity.java
new file mode 100644
index 0000000..284b485
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/tv/ParentalControlTestActivity.java
@@ -0,0 +1,149 @@
+/*
+ * 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 com.android.cts.verifier.tv;
+
+import com.android.cts.verifier.R;
+
+import android.content.Intent;
+import android.database.Cursor;
+import android.media.tv.TvContentRating;
+import android.media.tv.TvContract;
+import android.media.tv.TvInputManager;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+/**
+ * Tests for verifying TV app behavior on parental control.
+ */
+public class ParentalControlTestActivity extends TvAppVerifierActivity
+        implements View.OnClickListener {
+    private static final String TAG = "ParentalControlTestActivity";
+
+    private static final long TIMEOUT_MS = 5l * 60l * 1000l;  // 5 mins.
+
+    private View mTurnOnParentalControlItem;
+    private View mVerifyReceiveBroadcast1Item;
+    private View mBlockTvMaItem;
+    private View mVerifyReceiveBroadcast2Item;
+    private View mBlockUnblockItem;
+
+    private Intent mTvAppIntent = null;
+
+    @Override
+    public void onClick(View v) {
+        final View postTarget = getPostTarget();
+
+        if (containsButton(mTurnOnParentalControlItem, v)) {
+            final Runnable failCallback = new Runnable() {
+                @Override
+                public void run() {
+                    setPassState(mVerifyReceiveBroadcast1Item, false);
+                }
+            };
+            postTarget.postDelayed(failCallback, TIMEOUT_MS);
+            MockTvInputService.expectBroadcast(postTarget,
+                    TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED, new Runnable() {
+                @Override
+                public void run() {
+                    postTarget.removeCallbacks(failCallback);
+                    setPassState(mTurnOnParentalControlItem, true);
+                    setPassState(mVerifyReceiveBroadcast1Item, true);
+                    setButtonEnabled(mBlockTvMaItem, true);
+                }
+            });
+        } else if (containsButton(mBlockTvMaItem, v)) {
+            final Runnable failCallback = new Runnable() {
+                @Override
+                public void run() {
+                    setPassState(mVerifyReceiveBroadcast2Item, false);
+                }
+            };
+            postTarget.postDelayed(failCallback, TIMEOUT_MS);
+            MockTvInputService.expectBroadcast(postTarget,
+                    TvInputManager.ACTION_BLOCKED_RATINGS_CHANGED, new Runnable() {
+                @Override
+                public void run() {
+                    postTarget.removeCallbacks(failCallback);
+                    setPassState(mBlockTvMaItem, true);
+                    setPassState(mVerifyReceiveBroadcast2Item, true);
+                    setButtonEnabled(mBlockUnblockItem, true);
+                }
+            });
+        } else if (containsButton(mBlockUnblockItem, v)) {
+            final Runnable failCallback = new Runnable() {
+                @Override
+                public void run() {
+                    setPassState(mBlockUnblockItem, false);
+                }
+            };
+            postTarget.postDelayed(failCallback, TIMEOUT_MS);
+            MockTvInputService.setBlockRating(TvContentRating.createRating(
+                    "com.android.cts.verifier", "CTS_VERIFIER", "FAKE"));
+            MockTvInputService.expectUnblockContent(postTarget, new Runnable() {
+                @Override
+                public void run() {
+                    postTarget.removeCallbacks(failCallback);
+                    setPassState(mBlockUnblockItem, true);
+                    getPassButton().setEnabled(true);
+                }
+            });
+        }
+        if (mTvAppIntent == null) {
+            String[] projection = { TvContract.Channels._ID };
+            try (Cursor cursor = getContentResolver().query(TvContract.Channels.CONTENT_URI,
+                    projection, null, null, null)) {
+                if (cursor != null && cursor.moveToNext()) {
+                    mTvAppIntent = new Intent(Intent.ACTION_VIEW,
+                            TvContract.buildChannelUri(cursor.getLong(0)));
+                }
+            }
+            if (mTvAppIntent == null) {
+                Toast.makeText(this, R.string.tv_channel_not_found, Toast.LENGTH_SHORT).show();
+                return;
+            }
+        }
+        startActivity(mTvAppIntent);
+    }
+
+    @Override
+    protected void createTestItems() {
+        mTurnOnParentalControlItem = createUserItem(
+                R.string.tv_parental_control_test_turn_on_parental_control,
+                R.string.tv_launch_tv_app, this);
+        setButtonEnabled(mTurnOnParentalControlItem, true);
+        mVerifyReceiveBroadcast1Item = createAutoItem(
+                R.string.tv_parental_control_test_verify_receive_broadcast1);
+        mBlockTvMaItem = createUserItem(R.string.tv_parental_control_test_block_tv_ma,
+                R.string.tv_launch_tv_app, this);
+        mVerifyReceiveBroadcast2Item = createAutoItem(
+                R.string.tv_parental_control_test_verify_receive_broadcast2);
+        mBlockUnblockItem = createUserItem(R.string.tv_parental_control_test_block_unblock,
+                R.string.tv_launch_tv_app, this);
+    }
+
+    @Override
+    protected void setInfoResources() {
+        setInfoResources(R.string.tv_parental_control_test,
+                R.string.tv_parental_control_test_info, -1);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/tv/TvAppVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/tv/TvAppVerifierActivity.java
new file mode 100644
index 0000000..3529237
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/tv/TvAppVerifierActivity.java
@@ -0,0 +1,113 @@
+/*
+ * 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 com.android.cts.verifier.tv;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import android.content.Intent;
+import android.media.tv.TvContract;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+/**
+ * Base class for TV app tests.
+ */
+public abstract class TvAppVerifierActivity extends PassFailButtons.Activity {
+    private static final String TAG = "TvAppVerifierActivity";
+
+    private static final long TIMEOUT_MS = 5l * 60l * 1000l;  // 5 mins.
+
+    private LayoutInflater mInflater;
+    private ViewGroup mItemList;
+    private View mPostTarget;
+
+    protected View getPostTarget() {
+        return mPostTarget;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mInflater = getLayoutInflater();
+        // Reusing location_mode_main.
+        View view = mInflater.inflate(R.layout.location_mode_main, null);
+        mPostTarget = mItemList = (ViewGroup) view.findViewById(R.id.test_items);
+        createTestItems();
+        setContentView(view);
+        setPassFailButtonClickListeners();
+        setInfoResources();
+
+        getPassButton().setEnabled(false);
+    }
+
+    protected void setButtonEnabled(View item, boolean enabled) {
+        View button = item.findViewById(R.id.user_action_button);
+        button.setClickable(enabled);
+        button.setEnabled(enabled);
+    }
+
+    protected void setPassState(View item, boolean passed) {
+        ImageView status = (ImageView) item.findViewById(R.id.status);
+        status.setImageResource(passed ? R.drawable.fs_good : R.drawable.fs_error);
+        View button = item.findViewById(R.id.user_action_button);
+        button.setClickable(false);
+        button.setEnabled(false);
+        status.invalidate();
+    }
+
+    protected abstract void createTestItems();
+
+    protected abstract void setInfoResources();
+
+    /**
+     * Call this to create a test step where the user must perform some action.
+     */
+    protected View createUserItem(int instructionTextId, int buttonTextId, View.OnClickListener l) {
+        View item = mInflater.inflate(R.layout.tv_item, mItemList, false);
+        TextView instructions = (TextView) item.findViewById(R.id.instructions);
+        instructions.setText(instructionTextId);
+        Button button = (Button) item.findViewById(R.id.user_action_button);
+        button.setVisibility(View.VISIBLE);
+        button.setText(buttonTextId);
+        button.setOnClickListener(l);
+        mItemList.addView(item);
+        return item;
+    }
+
+    /**
+     * Call this to create a test step where the test automatically evaluates whether
+     * an expected condition is satisfied.
+     */
+    protected View createAutoItem(int stringId) {
+        View item = mInflater.inflate(R.layout.tv_item, mItemList, false);
+        TextView instructions = (TextView) item.findViewById(R.id.instructions);
+        instructions.setText(stringId);
+        mItemList.addView(item);
+        return item;
+    }
+
+    static boolean containsButton(View item, View button) {
+        return item.findViewById(R.id.user_action_button) == button;
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/tv/TvInputDiscoveryTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/tv/TvInputDiscoveryTestActivity.java
new file mode 100644
index 0000000..3d17a1a
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/tv/TvInputDiscoveryTestActivity.java
@@ -0,0 +1,123 @@
+/*
+ * 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 com.android.cts.verifier.tv;
+
+import android.content.Intent;
+import android.media.tv.TvContract;
+import android.view.View;
+
+import com.android.cts.verifier.R;
+
+/**
+ * Tests for verifying TV app behavior for third-party TV input apps.
+ */
+public class TvInputDiscoveryTestActivity extends TvAppVerifierActivity
+        implements View.OnClickListener {
+    private static final String TAG = "TvInputDiscoveryTestActivity";
+
+    private static final Intent TV_APP_INTENT = new Intent(Intent.ACTION_VIEW,
+            TvContract.buildChannelUri(0));
+
+    private static final long TIMEOUT_MS = 5l * 60l * 1000l;  // 5 mins.
+
+    private View mGoToSetupItem;
+    private View mVerifySetupItem;
+    private View mTuneToChannelItem;
+    private View mVerifyTuneItem;
+    private View mVerifyOverlayViewItem;
+    private boolean mTuneVerified;
+    private boolean mOverlayViewVerified;
+
+    @Override
+    public void onClick(View v) {
+        final View postTarget = getPostTarget();
+
+        if (containsButton(mGoToSetupItem, v)) {
+            final Runnable failCallback = new Runnable() {
+                @Override
+                public void run() {
+                    setPassState(mVerifySetupItem, false);
+                }
+            };
+            postTarget.postDelayed(failCallback, TIMEOUT_MS);
+            MockTvInputSetupActivity.expectLaunch(postTarget, new Runnable() {
+                @Override
+                public void run() {
+                    postTarget.removeCallbacks(failCallback);
+                    setPassState(mGoToSetupItem, true);
+                    setPassState(mVerifySetupItem, true);
+                    setButtonEnabled(mTuneToChannelItem, true);
+                }
+            });
+        } else if (containsButton(mTuneToChannelItem, v)) {
+            final Runnable failCallback = new Runnable() {
+                @Override
+                public void run() {
+                    setPassState(mVerifyTuneItem, false);
+                }
+            };
+            postTarget.postDelayed(failCallback, TIMEOUT_MS);
+            MockTvInputService.expectTune(postTarget, new Runnable() {
+                @Override
+                public void run() {
+                    setPassState(mTuneToChannelItem, true);
+                    setPassState(mVerifyTuneItem, true);
+
+                    mTuneVerified = true;
+                    updatePassState(postTarget, failCallback);
+                }
+            });
+            MockTvInputService.expectOverlayView(postTarget, new Runnable() {
+                @Override
+                public void run() {
+                    postTarget.removeCallbacks(failCallback);
+                    setPassState(mVerifyOverlayViewItem, true);
+
+                    mOverlayViewVerified = true;
+                    updatePassState(postTarget, failCallback);
+                }
+            });
+        }
+        startActivity(TV_APP_INTENT);
+    }
+
+    @Override
+    protected void createTestItems() {
+        mGoToSetupItem = createUserItem(R.string.tv_input_discover_test_go_to_setup,
+                R.string.tv_launch_tv_app, this);
+        setButtonEnabled(mGoToSetupItem, true);
+        mVerifySetupItem = createAutoItem(R.string.tv_input_discover_test_verify_setup);
+        mTuneToChannelItem = createUserItem(R.string.tv_input_discover_test_tune_to_channel,
+                R.string.tv_launch_tv_app, this);
+        mVerifyTuneItem = createAutoItem(R.string.tv_input_discover_test_verify_tune);
+        mVerifyOverlayViewItem = createAutoItem(
+                R.string.tv_input_discover_test_verify_overlay_view);
+    }
+
+    private void updatePassState(View postTarget, Runnable failCallback) {
+        if (mTuneVerified && mOverlayViewVerified) {
+            postTarget.removeCallbacks(failCallback);
+            getPassButton().setEnabled(true);
+        }
+    }
+
+    @Override
+    protected void setInfoResources() {
+        setInfoResources(R.string.tv_input_discover_test,
+                R.string.tv_input_discover_test_info, -1);
+    }
+}
diff --git a/apps/NotificationBot/Android.mk b/apps/NotificationBot/Android.mk
new file mode 100644
index 0000000..9d9c9f9
--- /dev/null
+++ b/apps/NotificationBot/Android.mk
@@ -0,0 +1,36 @@
+#
+# 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.
+#
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-Iaidl-files-under, src)
+
+LOCAL_PACKAGE_NAME := NotificationBot
+
+LOCAL_PROGUARD_FLAG_FILES := proguard.flags
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/apps/NotificationBot/AndroidManifest.xml b/apps/NotificationBot/AndroidManifest.xml
new file mode 100644
index 0000000..b63791f
--- /dev/null
+++ b/apps/NotificationBot/AndroidManifest.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2010 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="com.android.cts.robot"
+      android:versionCode="1"
+      android:versionName="1.0">
+
+    <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="21"/>
+
+    <application android:label="@string/app_name"
+            android:icon="@drawable/icon"
+            android:debuggable="true">
+
+        <!-- Required because a bare service won't show up in the app notifications list. -->
+        <activity android:name=".NotificationBotActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <!-- services used by the CtsVerifier to test notifications. -->
+        <receiver android:name=".NotificationBot" android:exported="true">
+            <intent-filter>
+                <action android:name="com.android.cts.robot.ACTION_POST" />
+                <action android:name="com.android.cts.robot.ACTION_CANCEL" />
+            </intent-filter>
+        </receiver>
+
+
+    </application>
+
+</manifest>
diff --git a/apps/NotificationBot/proguard.flags b/apps/NotificationBot/proguard.flags
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/apps/NotificationBot/proguard.flags
diff --git a/apps/NotificationBot/res/drawable-hdpi/icon.png b/apps/NotificationBot/res/drawable-hdpi/icon.png
new file mode 100644
index 0000000..ecaabbe
--- /dev/null
+++ b/apps/NotificationBot/res/drawable-hdpi/icon.png
Binary files differ
diff --git a/apps/NotificationBot/res/drawable-ldpi/icon.png b/apps/NotificationBot/res/drawable-ldpi/icon.png
new file mode 100644
index 0000000..f2de61f
--- /dev/null
+++ b/apps/NotificationBot/res/drawable-ldpi/icon.png
Binary files differ
diff --git a/apps/NotificationBot/res/drawable-mdpi/icon.png b/apps/NotificationBot/res/drawable-mdpi/icon.png
new file mode 100644
index 0000000..4950761
--- /dev/null
+++ b/apps/NotificationBot/res/drawable-mdpi/icon.png
Binary files differ
diff --git a/apps/NotificationBot/res/drawable-xhdpi/icon.png b/apps/NotificationBot/res/drawable-xhdpi/icon.png
new file mode 100644
index 0000000..9b39cfb
--- /dev/null
+++ b/apps/NotificationBot/res/drawable-xhdpi/icon.png
Binary files differ
diff --git a/apps/NotificationBot/res/drawable-xxhdpi/icon.png b/apps/NotificationBot/res/drawable-xxhdpi/icon.png
new file mode 100644
index 0000000..b944c10
--- /dev/null
+++ b/apps/NotificationBot/res/drawable-xxhdpi/icon.png
Binary files differ
diff --git a/apps/NotificationBot/res/layout/main.xml b/apps/NotificationBot/res/layout/main.xml
new file mode 100644
index 0000000..bf84fa9
--- /dev/null
+++ b/apps/NotificationBot/res/layout/main.xml
@@ -0,0 +1,36 @@
+<?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:layout_width="match_parent"
+             android:layout_height="match_parent"
+             android:orientation="vertical"
+        >
+
+    <Space android:layout_width="match_parent"
+           android:layout_height="0dp"
+           android:layout_weight="1"
+            />
+    <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/call_to_action"
+            android:textAlignment="center"
+            />
+    <Space android:layout_width="match_parent"
+           android:layout_height="0dp"
+           android:layout_weight="1"
+            />
+</LinearLayout>
\ No newline at end of file
diff --git a/apps/NotificationBot/res/values/strings.xml b/apps/NotificationBot/res/values/strings.xml
new file mode 100644
index 0000000..866a9ec
--- /dev/null
+++ b/apps/NotificationBot/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources>
+    <string name="app_name">CTS Robot</string>
+    <string name="call_to_action">Nothing to do here,\nPlease run the CTSVerifier App instead.</string>
+</resources>
\ No newline at end of file
diff --git a/apps/NotificationBot/src/com/android/cts/robot/NotificationBot.java b/apps/NotificationBot/src/com/android/cts/robot/NotificationBot.java
new file mode 100644
index 0000000..2aa5f41
--- /dev/null
+++ b/apps/NotificationBot/src/com/android/cts/robot/NotificationBot.java
@@ -0,0 +1,67 @@
+/*
+ * 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 com.android.cts.robot;
+
+import android.app.Activity;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+
+public class NotificationBot extends BroadcastReceiver {
+    private static final String TAG = "NotificationBot";
+    private static final String EXTRA_ID = "ID";
+    private static final String EXTRA_NOTIFICATION = "NOTIFICATION";
+    private static final String ACTION_POST = "com.android.cts.robot.ACTION_POST";
+    private static final String ACTION_CANCEL = "com.android.cts.robot.ACTION_CANCEL";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Log.i(TAG, "received intent: " + intent);
+        if (ACTION_POST.equals(intent.getAction())) {
+            Log.i(TAG, ACTION_POST);
+            if (!intent.hasExtra(EXTRA_NOTIFICATION) || !intent.hasExtra(EXTRA_ID)) {
+                Log.e(TAG, "received post action with missing content");
+                return;
+            }
+            int id = intent.getIntExtra(EXTRA_ID, -1);
+            Log.i(TAG, "id: " + id);
+            Notification n = (Notification) intent.getParcelableExtra(EXTRA_NOTIFICATION);
+            Log.i(TAG, "n: " + n);
+            NotificationManager noMa =
+                    (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+            noMa.notify(id, n);
+
+        } else if (ACTION_CANCEL.equals(intent.getAction())) {
+            Log.i(TAG, ACTION_CANCEL);
+            int id = intent.getIntExtra(EXTRA_ID, -1);
+            Log.i(TAG, "id: " + id);
+            if (id < 0) {
+                Log.e(TAG, "received cancel action with no ID");
+                return;
+            }
+            NotificationManager noMa =
+                    (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+            noMa.cancel(id);
+
+        } else {
+            Log.i(TAG, "received unexpected action: " + intent.getAction());
+        }
+    }
+}
diff --git a/common/util/tests/src/com/android/compatibility/common/util/CommonUtilTest.java b/apps/NotificationBot/src/com/android/cts/robot/NotificationBotActivity.java
similarity index 64%
copy from common/util/tests/src/com/android/compatibility/common/util/CommonUtilTest.java
copy to apps/NotificationBot/src/com/android/cts/robot/NotificationBotActivity.java
index a376373..1b9408e 100644
--- a/common/util/tests/src/com/android/compatibility/common/util/CommonUtilTest.java
+++ b/apps/NotificationBot/src/com/android/cts/robot/NotificationBotActivity.java
@@ -13,13 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package com.android.cts.robot;
 
-package com.android.compatibility.common.util;
+import android.app.Activity;
+import android.os.Bundle;
+import com.android.cts.robot.R;
 
-import junit.framework.TestCase;
-
-public class CommonUtilTest extends TestCase {
-
-    // TODO(stuartscott): Add tests when there is something to test.
-
-}
+public class NotificationBotActivity extends Activity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+    }
+}
\ No newline at end of file
diff --git a/build/test_host_java_library.mk b/build/test_host_java_library.mk
index baf9e75..8e071e4 100644
--- a/build/test_host_java_library.mk
+++ b/build/test_host_java_library.mk
@@ -21,14 +21,18 @@
 
 cts_library_xml := $(CTS_TESTCASES_OUT)/$(LOCAL_MODULE).xml
 
-$(cts_library_xml): PRIVATE_PATH := $(LOCAL_PATH)/src
+cts_src_dirs := $(LOCAL_PATH)/src
+cts_src_dirs += $(sort $(dir $(LOCAL_GENERATED_SOURCES)))
+cts_src_dirs := $(addprefix -s , $(cts_src_dirs))
+
+$(cts_library_xml): PRIVATE_SRC_DIRS := $(cts_src_dirs)
 $(cts_library_xml): PRIVATE_TEST_PACKAGE := $(LOCAL_CTS_TEST_PACKAGE)
 $(cts_library_xml): PRIVATE_LIBRARY := $(LOCAL_MODULE)
 $(cts_library_xml): PRIVATE_JAR_PATH := $(LOCAL_MODULE).jar
 $(cts_library_xml): $(HOST_OUT_JAVA_LIBRARIES)/$(LOCAL_MODULE).jar $(CTS_EXPECTATIONS) $(CTS_UNSUPPORTED_ABIS) $(CTS_JAVA_TEST_SCANNER_DOCLET) $(CTS_JAVA_TEST_SCANNER) $(CTS_XML_GENERATOR)
 	$(hide) echo Generating test description for host library $(PRIVATE_LIBRARY)
 	$(hide) mkdir -p $(CTS_TESTCASES_OUT)
-	$(hide) $(CTS_JAVA_TEST_SCANNER) -s $(PRIVATE_PATH) \
+	$(hide) $(CTS_JAVA_TEST_SCANNER) $(PRIVATE_SRC_DIRS) \
 						-d $(CTS_JAVA_TEST_SCANNER_DOCLET) | \
 			$(CTS_XML_GENERATOR) -t hostSideOnly \
 						-j $(PRIVATE_JAR_PATH) \
diff --git a/build/test_package.mk b/build/test_package.mk
index 353ae07..72972b2 100644
--- a/build/test_package.mk
+++ b/build/test_package.mk
@@ -28,12 +28,16 @@
 cts_package_apk := $(CTS_TESTCASES_OUT)/$(LOCAL_PACKAGE_NAME).apk
 cts_package_xml := $(CTS_TESTCASES_OUT)/$(LOCAL_PACKAGE_NAME).xml
 
+cts_src_dirs := $(LOCAL_PATH)
+cts_src_dirs += $(sort $(dir $(LOCAL_GENERATED_SOURCES)))
+cts_src_dirs := $(addprefix -s , $(cts_src_dirs))
+
 $(cts_package_apk): PRIVATE_PACKAGE := $(LOCAL_PACKAGE_NAME)
 $(cts_package_apk): $(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME))/package.apk | $(ACP)
 	$(hide) mkdir -p $(CTS_TESTCASES_OUT)
 	$(hide) $(ACP) -fp $(call intermediates-dir-for,APPS,$(PRIVATE_PACKAGE))/package.apk $@
 
-$(cts_package_xml): PRIVATE_PATH := $(LOCAL_PATH)
+$(cts_package_xml): PRIVATE_SRC_DIRS := $(cts_src_dirs)
 $(cts_package_xml): PRIVATE_INSTRUMENTATION := $(LOCAL_INSTRUMENTATION_FOR)
 $(cts_package_xml): PRIVATE_PACKAGE := $(LOCAL_PACKAGE_NAME)
 ifneq ($(filter cts/suite/cts/%, $(LOCAL_PATH)),)
@@ -48,7 +52,7 @@
 	$(hide) echo Generating test description for java package $(PRIVATE_PACKAGE)
 	$(hide) mkdir -p $(CTS_TESTCASES_OUT)
 	$(hide) $(CTS_JAVA_TEST_SCANNER) \
-						-s $(PRIVATE_PATH) \
+						$(PRIVATE_SRC_DIRS) \
 						-d $(CTS_JAVA_TEST_SCANNER_DOCLET) | \
 			$(CTS_XML_GENERATOR) \
 						-t $(PRIVATE_TEST_TYPE) \
diff --git a/build/test_uiautomator.mk b/build/test_uiautomator.mk
index 5e2f07a..085d672 100644
--- a/build/test_uiautomator.mk
+++ b/build/test_uiautomator.mk
@@ -24,12 +24,16 @@
 cts_library_xml := $(CTS_TESTCASES_OUT)/$(LOCAL_MODULE).xml 
 cts_library_jar := $(CTS_TESTCASES_OUT)/$(LOCAL_MODULE).jar
 
+cts_src_dirs := $(LOCAL_PATH)/src
+cts_src_dirs += $(sort $(dir $(LOCAL_GENERATED_SOURCES)))
+cts_src_dirs := $(addprefix -s , $(cts_src_dirs))
+
 $(cts_library_jar): PRIVATE_MODULE := $(LOCAL_MODULE)
 $(cts_library_jar): $(call intermediates-dir-for,JAVA_LIBRARIES,$(LOCAL_MODULE))/javalib.jar | $(ACP)
 	$(hide) mkdir -p $(CTS_TESTCASES_OUT)
 	$(hide) $(ACP) -fp $(call intermediates-dir-for,JAVA_LIBRARIES,$(PRIVATE_MODULE))/javalib.jar $@
 
-$(cts_library_xml): PRIVATE_PATH := $(LOCAL_PATH)/src
+$(cts_library_xml): PRIVATE_SRC_DIRS := $(cts_src_dirs)
 $(cts_library_xml): PRIVATE_TEST_APP_PACKAGE := $(LOCAL_CTS_TEST_APP_PACKAGE)
 $(cts_library_xml): PRIVATE_TEST_PACKAGE := $(LOCAL_CTS_TEST_PACKAGE)
 $(cts_library_xml): PRIVATE_TEST_APK := $(LOCAL_CTS_TEST_APK)
@@ -38,7 +42,7 @@
 $(cts_library_xml): $(call intermediates-dir-for,JAVA_LIBRARIES,$(LOCAL_MODULE))/javalib.jar $(CTS_EXPECTATIONS) $(CTS_UNSUPPORTED_ABIS) $(CTS_JAVA_TEST_SCANNER_DOCLET) $(CTS_JAVA_TEST_SCANNER) $(CTS_XML_GENERATOR)
 	$(hide) echo Generating test description for uiautomator library $(PRIVATE_LIBRARY)
 	$(hide) mkdir -p $(CTS_TESTCASES_OUT)
-	$(hide) $(CTS_JAVA_TEST_SCANNER) -s $(PRIVATE_PATH) \
+	$(hide) $(CTS_JAVA_TEST_SCANNER) $(PRIVATE_SRC_DIRS) \
 						-d $(CTS_JAVA_TEST_SCANNER_DOCLET) | \
 			$(CTS_XML_GENERATOR) -t uiAutomator \
 						-i $(PRIVATE_TEST_APK) \
diff --git a/common/util/Android.mk b/common/util/Android.mk
index b7842559..84ced65 100644
--- a/common/util/Android.mk
+++ b/common/util/Android.mk
@@ -42,6 +42,8 @@
 
 LOCAL_MODULE := compatibility-common-util-hostsidelib_v2
 
+LOCAL_STATIC_JAVA_LIBRARIES := kxml2-2.3.0
+
 include $(BUILD_HOST_JAVA_LIBRARY)
 
 ###############################################################################
@@ -52,7 +54,10 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, tests/src)
 
-LOCAL_JAVA_LIBRARIES := junit
+LOCAL_STATIC_JAVA_LIBRARIES := \
+                        junit \
+                        kxml2-2.3.0 \
+                        compatibility-common-util-hostsidelib_v2
 
 LOCAL_MODULE := compatibility-common-util-tests_v2
 
diff --git a/common/util/run_unit_tests.sh b/common/util/run_unit_tests.sh
new file mode 100755
index 0000000..04a6745
--- /dev/null
+++ b/common/util/run_unit_tests.sh
@@ -0,0 +1,49 @@
+#!/bin/bash
+
+# Copyright (C) 2012 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.
+
+# helper script for running the cts common unit tests
+
+checkFile() {
+    if [ ! -f "$1" ]; then
+        echo "Unable to locate $1"
+        exit
+    fi;
+}
+
+# check if in Android build env
+if [ ! -z ${ANDROID_BUILD_TOP} ]; then
+    HOST=`uname`
+    if [ "$HOST" == "Linux" ]; then
+        OS="linux-x86"
+    elif [ "$HOST" == "Darwin" ]; then
+        OS="darwin-x86"
+    else
+        echo "Unrecognized OS"
+        exit
+    fi;
+fi;
+
+JAR_DIR=${ANDROID_BUILD_TOP}/out/host/$OS/framework
+JARS="tradefed-prebuilt.jar compatibility-common-util-hostsidelib_v2.jar compatibility-common-util-tests_v2.jar"
+
+for JAR in $JARS; do
+    checkFile ${JAR_DIR}/${JAR}
+    JAR_PATH=${JAR_PATH}:${JAR_DIR}/${JAR}
+done
+
+java $RDBG_FLAG \
+  -cp ${JAR_PATH} com.android.tradefed.command.Console run singleCommand host -n --class com.android.compatibility.common.util.UnitTests "$@"
+
diff --git a/common/util/src/com/android/compatibility/common/util/MetricsXmlSerializer.java b/common/util/src/com/android/compatibility/common/util/MetricsXmlSerializer.java
new file mode 100644
index 0000000..0e2b004
--- /dev/null
+++ b/common/util/src/com/android/compatibility/common/util/MetricsXmlSerializer.java
@@ -0,0 +1,70 @@
+/*
+ * 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 com.android.compatibility.common.util;
+
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Serialize Metric data from {@link ReportLog} into compatibility report friendly XML
+ */
+public final class MetricsXmlSerializer {
+
+    private final XmlSerializer mXmlSerializer;
+
+    public MetricsXmlSerializer(XmlSerializer xmlSerializer) {
+        this.mXmlSerializer = xmlSerializer;
+    }
+
+    public void serialize(ReportLog reportLog) throws IOException {
+        if (reportLog == null) {
+            return;
+        }
+        ReportLog.Result summary = reportLog.getSummary();
+        List<ReportLog.Result> detailedMetrics = reportLog.getDetailedMetrics();
+        // <Summary message="Average" scoreType="lower_better" unit="ms">195.2</Summary>
+        if (summary != null) {
+            mXmlSerializer.startTag(null, "Summary");
+            mXmlSerializer.attribute(null, "message", summary.getMessage());
+            mXmlSerializer.attribute(null, "scoreType", summary.getType().getXmlString());
+            mXmlSerializer.attribute(null, "unit", summary.getUnit().getXmlString());
+            mXmlSerializer.text(Double.toString(summary.getValues()[0]));
+            mXmlSerializer.endTag(null, "Summary");
+        }
+
+        if (!detailedMetrics.isEmpty()) {
+            mXmlSerializer.startTag(null, "Details");
+            for (ReportLog.Result result : detailedMetrics) {
+                mXmlSerializer.startTag(null, "ValueArray");
+                mXmlSerializer.attribute(null, "source", result.getLocation());
+                mXmlSerializer.attribute(null, "message", result.getMessage());
+                mXmlSerializer.attribute(null, "scoreType", result.getType().getXmlString());
+                mXmlSerializer.attribute(null, "unit", result.getUnit().getXmlString());
+
+                for (double value : result.getValues()) {
+                    mXmlSerializer.startTag(null, "Value");
+                    mXmlSerializer.text(Double.toString(value));
+                    mXmlSerializer.endTag(null, "Value");
+                }
+                mXmlSerializer.endTag(null, "ValueArray");
+            }
+            mXmlSerializer.endTag(null, "Details");
+        }
+    }
+}
diff --git a/common/util/src/com/android/compatibility/common/util/ReportLog.java b/common/util/src/com/android/compatibility/common/util/ReportLog.java
index 9e733e4..8cfc086 100644
--- a/common/util/src/com/android/compatibility/common/util/ReportLog.java
+++ b/common/util/src/com/android/compatibility/common/util/ReportLog.java
@@ -16,6 +16,9 @@
 
 package com.android.compatibility.common.util;
 
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
 import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.List;
@@ -28,8 +31,8 @@
     private Result mSummary;
     private final List<Result> mDetails = new ArrayList<Result>();
 
-    private class Result implements Serializable {
-        private static final int BASE_DEPTH = 2;// 0:constructor, 1:addValues/setSummary, 2:caller
+    class Result implements Serializable {
+        private static final int CALLER_STACKTRACE_DEPTH = 5;
         private String mLocation;
         private String mMessage;
         private double[] mValues;
@@ -53,15 +56,35 @@
         private Result(String message, double[] values, ResultType type,
                 ResultUnit unit, int depth) {
             final StackTraceElement[] trace = Thread.currentThread().getStackTrace();
-            final StackTraceElement e = trace[Math.min(BASE_DEPTH + depth, trace.length - 1)];
-            mLocation = String.format("%s#%s:%d",
-                    e.getClassName(), e.getMethodName(), e.getLineNumber());
+            final StackTraceElement e =
+                    trace[Math.min(CALLER_STACKTRACE_DEPTH + depth, trace.length - 1)];
+            mLocation = String.format(
+                    "%s#%s:%d", e.getClassName(), e.getMethodName(), e.getLineNumber());
             mMessage = message;
             mValues = values;
             mType = type;
             mUnit = unit;
         }
 
+        public String getLocation() {
+            return mLocation;
+        }
+
+        public String getMessage() {
+            return mMessage;
+        }
+
+        public double[] getValues() {
+            return mValues;
+        }
+
+        public ResultType getType() {
+            return mType;
+        }
+
+        public ResultUnit getUnit() {
+            return mUnit;
+        }
     }
 
     /**
@@ -108,4 +131,12 @@
             ResultUnit unit, int depth) {
         mSummary = new Result(message, new double[] {value}, type, unit, depth);
     }
+
+    public Result getSummary() {
+        return mSummary;
+    }
+
+    public List<Result> getDetailedMetrics() {
+        return new ArrayList<Result>(mDetails);
+    }
 }
diff --git a/common/util/tests/src/com/android/compatibility/common/util/MetricsXmlSerializerTest.java b/common/util/tests/src/com/android/compatibility/common/util/MetricsXmlSerializerTest.java
new file mode 100644
index 0000000..70da820
--- /dev/null
+++ b/common/util/tests/src/com/android/compatibility/common/util/MetricsXmlSerializerTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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 com.android.compatibility.common.util;
+
+import junit.framework.TestCase;
+
+import org.xmlpull.v1.XmlPullParserFactory;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * Unit tests for {@link MetricsXmlSerializer}
+ */
+public class MetricsXmlSerializerTest extends TestCase {
+
+    static class LocalReportLog extends ReportLog {}
+    private static final double[] VALUES = new double[] {1, 11, 21, 1211, 111221};
+    private static final String HEADER = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>";
+    private static final String EXPECTED_XML =
+            HEADER
+            + "<Summary message=\"Sample\" scoreType=\"higher_better\" unit=\"byte\">1.0</Summary>"
+            + "<Details>"
+                    + "<ValueArray source=\"sun.reflect.NativeMethodAccessorImpl#invoke0:-2\""
+                    + " message=\"Details\" scoreType=\"neutral\" unit=\"fps\">"
+                        + "<Value>1.0</Value>"
+                        + "<Value>11.0</Value>"
+                        + "<Value>21.0</Value>"
+                        + "<Value>1211.0</Value>"
+                        + "<Value>111221.0</Value>"
+                    + "</ValueArray>"
+            + "</Details>";
+
+    private LocalReportLog mLocalReportLog;
+    private MetricsXmlSerializer mMetricsXmlSerializer;
+    private ByteArrayOutputStream mByteArrayOutputStream;
+    private XmlSerializer xmlSerializer;
+
+    @Override
+    public void setUp() throws Exception {
+        mLocalReportLog = new LocalReportLog();
+        mByteArrayOutputStream = new ByteArrayOutputStream();
+        XmlPullParserFactory factory = XmlPullParserFactory.newInstance(null, null);
+        xmlSerializer = factory.newSerializer();
+        xmlSerializer.setOutput(mByteArrayOutputStream, "utf-8");
+
+        this.mMetricsXmlSerializer = new MetricsXmlSerializer(xmlSerializer);
+    }
+
+    public void testSerialize_null() throws IOException {
+        xmlSerializer.startDocument("utf-8", true);
+        mMetricsXmlSerializer.serialize(null);
+        xmlSerializer.endDocument();
+
+        assertEquals(HEADER.length(), mByteArrayOutputStream.toByteArray().length);
+    }
+
+    public void testSerialize_noData() throws IOException {
+        xmlSerializer.startDocument("utf-8", true);
+        mMetricsXmlSerializer.serialize(mLocalReportLog);
+        xmlSerializer.endDocument();
+
+        assertEquals(HEADER.length(), mByteArrayOutputStream.toByteArray().length);
+    }
+
+    public void testSerialize() throws IOException {
+        mLocalReportLog.setSummary("Sample", 1.0, ResultType.HIGHER_BETTER, ResultUnit.BYTE);
+        mLocalReportLog.addValues("Details", VALUES, ResultType.NEUTRAL, ResultUnit.FPS);
+
+        xmlSerializer.startDocument("utf-8", true);
+        mMetricsXmlSerializer.serialize(mLocalReportLog);
+        xmlSerializer.endDocument();
+
+        assertEquals(EXPECTED_XML, mByteArrayOutputStream.toString("utf-8"));
+    }
+}
diff --git a/common/util/tests/src/com/android/compatibility/common/util/CommonUtilTest.java b/common/util/tests/src/com/android/compatibility/common/util/UnitTests.java
similarity index 70%
copy from common/util/tests/src/com/android/compatibility/common/util/CommonUtilTest.java
copy to common/util/tests/src/com/android/compatibility/common/util/UnitTests.java
index a376373..b9a17e1 100644
--- a/common/util/tests/src/com/android/compatibility/common/util/CommonUtilTest.java
+++ b/common/util/tests/src/com/android/compatibility/common/util/UnitTests.java
@@ -11,15 +11,21 @@
  * 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.
+ * limitations under the License
  */
 
 package com.android.compatibility.common.util;
 
-import junit.framework.TestCase;
+import junit.framework.TestSuite;
 
-public class CommonUtilTest extends TestCase {
+/**
+ * A {@link TestSuite} for the common.util package.
+ */
+public class UnitTests extends TestSuite {
 
-    // TODO(stuartscott): Add tests when there is something to test.
+    public UnitTests() {
+        super();
 
+        addTestSuite(MetricsXmlSerializerTest.class);
+    }
 }
diff --git a/hostsidetests/aadb/src/com/android/cts/aadb/TestDeviceFuncTest.java b/hostsidetests/aadb/src/com/android/cts/aadb/TestDeviceFuncTest.java
index ceb9072..9f69242 100644
--- a/hostsidetests/aadb/src/com/android/cts/aadb/TestDeviceFuncTest.java
+++ b/hostsidetests/aadb/src/com/android/cts/aadb/TestDeviceFuncTest.java
@@ -38,6 +38,7 @@
 import java.io.FileOutputStream;
 import java.io.InputStream;
 import java.io.IOException;
+import java.util.TimeZone;
 
 import javax.imageio.ImageIO;
 
@@ -289,6 +290,13 @@
             stream.write(testString.getBytes());
             stream.close();
 
+            // adjust 1st file's last-modified timestamp according to persist.sys.timezone
+            String deviceTimezone = mTestDevice.getProperty("persist.sys.timezone");
+            if (deviceTimezone != null) {
+                TimeZone tz = TimeZone.getTimeZone(deviceTimezone);
+                tmpFile.setLastModified(tmpFile.lastModified() + tz.getRawOffset());
+            }
+
             assertTrue(mTestDevice.syncFiles(tmpDir, externalStorePath));
             String tmpFileContents = mTestDevice.executeShellCommand(String.format("cat %s",
                     expectedDeviceFilePath));
diff --git a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java
index fe8f9ee..98610a0 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java
+++ b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java
@@ -27,6 +27,8 @@
 import android.provider.DocumentsProvider;
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.UiObject;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.UiScrollable;
 import android.support.test.uiautomator.UiSelector;
 import android.test.InstrumentationTestCase;
 import android.test.MoreAsserts;
@@ -66,6 +68,39 @@
         mActivity.finish();
     }
 
+    private UiObject findRoot(String label) throws UiObjectNotFoundException {
+        final UiSelector rootsList = new UiSelector().resourceId(
+                "com.android.documentsui:id/container_roots").childSelector(
+                new UiSelector().resourceId("android:id/list"));
+
+        // Wait for the first list item to appear
+        assertTrue("First list item",
+                new UiObject(rootsList.childSelector(new UiSelector())).waitForExists(TIMEOUT));
+
+        // Now scroll around to find our item
+        new UiScrollable(rootsList).scrollIntoView(new UiSelector().text(label));
+        return new UiObject(rootsList.childSelector(new UiSelector().text(label)));
+    }
+
+    private UiObject findDocument(String label) throws UiObjectNotFoundException {
+        final UiSelector docList = new UiSelector().resourceId(
+                "com.android.documentsui:id/container_directory").childSelector(
+                new UiSelector().resourceId("com.android.documentsui:id/list"));
+
+        // Wait for the first list item to appear
+        assertTrue("First list item",
+                new UiObject(docList.childSelector(new UiSelector())).waitForExists(TIMEOUT));
+
+        // Now scroll around to find our item
+        new UiScrollable(docList).scrollIntoView(new UiSelector().text(label));
+        return new UiObject(docList.childSelector(new UiSelector().text(label)));
+    }
+
+    private UiObject findSaveButton() throws UiObjectNotFoundException {
+        return new UiObject(new UiSelector().resourceId("com.android.documentsui:id/container_save")
+                .childSelector(new UiSelector().resourceId("android:id/button1")));
+    }
+
     public void testOpenSimple() throws Exception {
         if (!supportedHardware()) return;
 
@@ -83,16 +118,16 @@
 
         // Ensure that we see both of our roots
         mDevice.waitForIdle();
-        assertTrue("CtsLocal root", new UiObject(new UiSelector().text("CtsLocal")).waitForExists(TIMEOUT));
-        assertTrue("CtsCreate root", new UiObject(new UiSelector().text("CtsCreate")).exists());
-        assertFalse("CtsGetContent", new UiObject(new UiSelector().text("CtsGetContent")).exists());
+        assertTrue("CtsLocal root", findRoot("CtsLocal").exists());
+        assertTrue("CtsCreate root", findRoot("CtsCreate").exists());
+        assertFalse("CtsGetContent root", findRoot("CtsGetContent").exists());
 
         // Pick a specific file from our test provider
         mDevice.waitForIdle();
-        new UiObject(new UiSelector().text("CtsLocal")).click();
+        findRoot("CtsLocal").click();
 
         mDevice.waitForIdle();
-        new UiObject(new UiSelector().text("FILE1")).click();
+        findDocument("FILE1").click();
 
         final Result result = mActivity.getResult();
         final Uri uri = result.data.getData();
@@ -118,10 +153,10 @@
         mActivity.startActivityForResult(intent, 42);
 
         mDevice.waitForIdle();
-        new UiObject(new UiSelector().text("CtsCreate")).click();
+        findRoot("CtsCreate").click();
+
         mDevice.waitForIdle();
-        new UiObject(new UiSelector().resourceId("com.android.documentsui:id/container_save")
-                .childSelector(new UiSelector().resourceId("android:id/button1"))).click();
+        findSaveButton().click();
 
         final Result result = mActivity.getResult();
         final Uri uri = result.data.getData();
@@ -142,18 +177,17 @@
         mActivity.startActivityForResult(intent, 42);
 
         mDevice.waitForIdle();
-        new UiObject(new UiSelector().text("CtsCreate")).click();
+        findRoot("CtsCreate").click();
 
         // Pick file2, which should be selected since MIME matches, then try
         // picking a non-matching MIME, which should leave file2 selected.
         mDevice.waitForIdle();
-        new UiObject(new UiSelector().text("FILE2")).click();
+        findDocument("FILE2").click();
         mDevice.waitForIdle();
-        new UiObject(new UiSelector().text("FILE1")).click();
+        findDocument("FILE1").click();
 
         mDevice.waitForIdle();
-        new UiObject(new UiSelector().resourceId("com.android.documentsui:id/container_save")
-                .childSelector(new UiSelector().resourceId("android:id/button1"))).click();
+        findSaveButton().click();
 
         final Result result = mActivity.getResult();
         final Uri uri = result.data.getData();
@@ -168,12 +202,12 @@
         mActivity.startActivityForResult(intent, 42);
 
         mDevice.waitForIdle();
-        new UiObject(new UiSelector().text("CtsCreate")).click();
+        findRoot("CtsCreate").click();
+
         mDevice.waitForIdle();
-        new UiObject(new UiSelector().text("DIR2")).click();
+        findDocument("DIR2").click();
         mDevice.waitForIdle();
-        new UiObject(new UiSelector().resourceId("com.android.documentsui:id/container_save")
-                .childSelector(new UiSelector().resourceId("android:id/button1"))).click();
+        findSaveButton().click();
 
         final Result result = mActivity.getResult();
         final Uri uri = result.data.getData();
@@ -244,12 +278,12 @@
         // Look around, we should be able to see both DocumentsProviders and
         // other GET_CONTENT sources.
         mDevice.waitForIdle();
-        assertTrue("CtsLocal root", new UiObject(new UiSelector().text("CtsLocal")).waitForExists(TIMEOUT));
-        assertTrue("CtsCreate root", new UiObject(new UiSelector().text("CtsCreate")).exists());
-        assertTrue("CtsGetContent", new UiObject(new UiSelector().text("CtsGetContent")).exists());
+        assertTrue("CtsLocal root", findRoot("CtsLocal").exists());
+        assertTrue("CtsCreate root", findRoot("CtsCreate").exists());
+        assertTrue("CtsGetContent root", findRoot("CtsGetContent").exists());
 
         mDevice.waitForIdle();
-        new UiObject(new UiSelector().text("CtsGetContent")).click();
+        findRoot("CtsGetContent").click();
 
         final Result result = mActivity.getResult();
         assertEquals("ReSuLt", result.data.getAction());
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
index 5378266..cf16307 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
@@ -429,6 +429,7 @@
         boolean mHaveResult = false;
         boolean mGoodResult = false;
         boolean mSucceeded = false;
+        static final int TIMEOUT_MS = 30000;
         
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -456,10 +457,10 @@
                 final long startTime = SystemClock.uptimeMillis();
                 while (!mHaveResult) {
                     try {
-                        wait(5000);
+                        wait(TIMEOUT_MS);
                     } catch (InterruptedException e) {
                     }
-                    if (SystemClock.uptimeMillis() >= (startTime+5000)) {
+                    if (SystemClock.uptimeMillis() >= (startTime + TIMEOUT_MS)) {
                         throw new RuntimeException("Timeout");
                     }
                 }
@@ -477,10 +478,10 @@
                 final long startTime = SystemClock.uptimeMillis();
                 while (!mHaveResult) {
                     try {
-                        wait(5000);
+                        wait(TIMEOUT_MS);
                     } catch (InterruptedException e) {
                     }
-                    if (SystemClock.uptimeMillis() >= (startTime+5000)) {
+                    if (SystemClock.uptimeMillis() >= (startTime + TIMEOUT_MS)) {
                         throw new RuntimeException("Timeout");
                     }
                 }
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
index 7a196bf..dfc7e9c 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
@@ -49,6 +49,14 @@
 
         <activity
             android:name="com.android.cts.deviceowner.LockTaskUtilityActivity" />
+
+        <!-- we need to give a different taskAffinity so that when we use
+             FLAG_ACTIVITY_NEW_TASK, the system tries to start it in a different task
+        -->
+        <activity
+            android:name="com.android.cts.deviceowner.LockTaskTest$IntentReceivingActivity"
+            android:taskAffinity="com.android.cts.deviceowner.LockTaskTest.IntentReceivingActivity"
+            />
         <activity
             android:name="com.android.cts.deviceowner.ApplicationRestrictionActivity" />
     </application>
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/LockTaskTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/LockTaskTest.java
index 42aa847..69c5bf7 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/LockTaskTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/LockTaskTest.java
@@ -15,12 +15,14 @@
  */
 package com.android.cts.deviceowner;
 
+import android.app.Activity;
 import android.app.ActivityManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.provider.Settings;
+import android.os.Bundle;
 
 // This is not a standard test of an android activity (such as
 // ActivityInstrumentationTestCase2) as it is attempting to test the actual
@@ -30,9 +32,12 @@
 
     private static final String TEST_PACKAGE = "com.google.android.example.somepackage";
 
-    private static final int ACTIVITY_RESUMED_TIMEOUT_MILLIS = 60000;  // 60 seconds
-    private static final int ACTIVITY_RUNNING_TIMEOUT_MILLIS = 20000;  // 20 seconds
+    private static final int ACTIVITY_RESUMED_TIMEOUT_MILLIS = 20000;  // 20 seconds
+    private static final int ACTIVITY_RUNNING_TIMEOUT_MILLIS = 10000;  // 10 seconds
+    private static final int ACTIVITY_DESTROYED_TIMEOUT_MILLIS = 60000;  // 60 seconds
 
+    public static final String RECEIVING_ACTIVITY_CREATED_ACTION
+            = "com.android.cts.deviceowner.RECEIVER_ACTIVITY_STARTED_ACTION";
     /**
      * The tests below need to keep detailed track of the state of the activity
      * that is started and stopped frequently.  To do this it sends a number of
@@ -71,14 +76,30 @@
                     mIntentHandled = true;
                     LockTaskTest.this.notify();
                 }
+            } else if (RECEIVING_ACTIVITY_CREATED_ACTION.equals(action)) {
+                synchronized(mReceivingActivityCreatedLock) {
+                    mReceivingActivityWasCreated = true;
+                    mReceivingActivityCreatedLock.notify();
+                }
             }
         }
     };
 
+    public static class IntentReceivingActivity extends Activity {
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            sendBroadcast(new Intent(RECEIVING_ACTIVITY_CREATED_ACTION));
+            finish();
+        }
+    }
+
     private boolean mIsActivityRunning;
     private boolean mIsActivityResumed;
+    private boolean mReceivingActivityWasCreated;
     private final Object mActivityRunningLock = new Object();
     private final Object mActivityResumedLock = new Object();
+    private final Object mReceivingActivityCreatedLock = new Object();
     private Boolean mIntentHandled;
 
     @Override
@@ -90,6 +111,7 @@
         filter.addAction(LockTaskUtilityActivity.INTENT_ACTION);
         filter.addAction(LockTaskUtilityActivity.RESUME_ACTION);
         filter.addAction(LockTaskUtilityActivity.PAUSE_ACTION);
+        filter.addAction(RECEIVING_ACTIVITY_CREATED_ACTION);
         mContext.registerReceiver(mReceiver, filter);
     }
 
@@ -139,49 +161,46 @@
         stopAndFinish(activityManager);
     }
 
-    // This test has the UtilityActivity trigger starting another activity (settings)
+    // This launches an activity that is in the current task.
     // this should be permitted as a part of lock task (since it isn't a new task).
-    // As a result onPause should be called as it goes to a new activity.
     public void testStartActivityWithinTask() {
         mDevicePolicyManager.setLockTaskPackages(getWho(), new String[] { PACKAGE_NAME });
         startLockTask();
         waitForResume();
 
-        Intent launchIntent = new Intent(Settings.ACTION_SETTINGS);
+        mReceivingActivityWasCreated = false;
+        Intent launchIntent = new Intent(mContext, IntentReceivingActivity.class);
         Intent lockTaskUtility = getLockTaskUtility();
         lockTaskUtility.putExtra(LockTaskUtilityActivity.START_ACTIVITY, launchIntent);
         mContext.startActivity(lockTaskUtility);
 
-        synchronized (mActivityResumedLock) {
-            if (mIsActivityResumed) {
-                try {
-                    mActivityResumedLock.wait(ACTIVITY_RESUMED_TIMEOUT_MILLIS);
-                } catch (InterruptedException e) {
-                }
-                assertFalse(mIsActivityResumed);
+        synchronized (mReceivingActivityCreatedLock) {
+            try {
+                mReceivingActivityCreatedLock.wait(ACTIVITY_RESUMED_TIMEOUT_MILLIS);
+            } catch (InterruptedException e) {
             }
+            assertTrue(mReceivingActivityWasCreated);
         }
         stopAndFinish(null);
     }
 
     // This launches an activity that is not part of the current task and therefore
-    // should be blocked.  This is verified by making sure that the activity does
-    // not get a call to onPause.
+    // should be blocked.
     public void testCannotStartActivityOutsideTask() {
         mDevicePolicyManager.setLockTaskPackages(getWho(), new String[] { PACKAGE_NAME });
         startLockTask();
         waitForResume();
 
-        Intent launchIntent = new Intent(Settings.ACTION_SETTINGS);
+        mReceivingActivityWasCreated = false;
+        Intent launchIntent = new Intent(mContext, IntentReceivingActivity.class);
         launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         mContext.startActivity(launchIntent);
-
-        synchronized (mActivityResumedLock) {
+        synchronized (mReceivingActivityCreatedLock) {
             try {
-                mActivityResumedLock.wait(ACTIVITY_RESUMED_TIMEOUT_MILLIS);
+                mReceivingActivityCreatedLock.wait(ACTIVITY_RESUMED_TIMEOUT_MILLIS);
             } catch (InterruptedException e) {
             }
-            assertTrue(mIsActivityResumed);
+            assertFalse(mReceivingActivityWasCreated);
         }
         stopAndFinish(null);
     }
@@ -212,7 +231,7 @@
             finish();
             if (mIsActivityRunning) {
                 try {
-                    mActivityRunningLock.wait(ACTIVITY_RUNNING_TIMEOUT_MILLIS);
+                    mActivityRunningLock.wait(ACTIVITY_DESTROYED_TIMEOUT_MILLIS);
                 } catch (InterruptedException e) {
                 }
             }
@@ -220,7 +239,7 @@
     }
 
     /**
-     * Wait for onPause to be called on the LockTaskUtilityActivity.
+     * Wait for onResume to be called on the LockTaskUtilityActivity.
      */
     private void waitForResume() {
         // It may take a moment for the resume to come in.
diff --git a/hostsidetests/devicepolicy/app/LauncherTests/Android.mk b/hostsidetests/devicepolicy/app/LauncherTests/Android.mk
new file mode 100644
index 0000000..4517ea2
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/LauncherTests/Android.mk
@@ -0,0 +1,31 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsLauncherAppsTests
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/devicepolicy/app/LauncherTests/AndroidManifest.xml b/hostsidetests/devicepolicy/app/LauncherTests/AndroidManifest.xml
new file mode 100644
index 0000000..a21b8c2
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/LauncherTests/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.launchertests">
+
+    <uses-sdk android:minSdkVersion="21"/>
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.test.InstrumentationTestRunner"
+                     android:targetPackage="com.android.cts.launchertests"
+                     android:label="Launcher Apps CTS Tests"/>
+</manifest>
diff --git a/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/LauncherAppsTests.java b/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/LauncherAppsTests.java
new file mode 100644
index 0000000..3d44ecd
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/LauncherAppsTests.java
@@ -0,0 +1,287 @@
+/*
+ * 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 com.android.cts.launchertests;
+
+import android.app.admin.DeviceAdminReceiver;
+import android.app.admin.DevicePolicyManager;
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.test.InstrumentationTestCase;
+import android.test.InstrumentationTestRunner;
+import android.util.Pair;
+
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.List;
+
+/**
+ * Tests for LauncherApps service
+ */
+public class LauncherAppsTests extends InstrumentationTestCase {
+
+    public static final String SIMPLE_APP_PACKAGE = "com.android.cts.launcherapps.simpleapp";
+
+    public static final String USER_EXTRA = "user_extra";
+    public static final String PACKAGE_EXTRA = "package_extra";
+    public static final String REPLY_EXTRA = "reply_extra";
+
+    public static final int MSG_RESULT = 0;
+    public static final int MSG_CHECK_PACKAGE_ADDED = 1;
+    public static final int MSG_CHECK_PACKAGE_REMOVED = 2;
+    public static final int MSG_CHECK_PACKAGE_CHANGED = 3;
+    public static final int MSG_CHECK_NO_CALLBACK = 4;
+
+    public static final int RESULT_PASS = 1;
+    public static final int RESULT_FAIL = 2;
+    public static final int RESULT_TIMEOUT = 3;
+
+    private LauncherApps mLauncherApps;
+    private UserHandle mUser;
+    private InstrumentationTestRunner mInstrumentation;
+    private Messenger mService;
+    private Connection mConnection;
+    private Result mResult;
+    private Messenger mResultMessenger;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mInstrumentation = (InstrumentationTestRunner) getInstrumentation();
+        Bundle arguments = mInstrumentation.getArguments();
+        UserManager userManager = (UserManager) mInstrumentation.getContext().getSystemService(
+                Context.USER_SERVICE);
+        mUser = getUserHandleArgument(userManager, "testUser", arguments);
+        mLauncherApps = (LauncherApps) mInstrumentation.getContext().getSystemService(
+                Context.LAUNCHER_APPS_SERVICE);
+
+        final Intent intent = new Intent();
+        intent.setComponent(new ComponentName("com.android.cts.launchertests.support",
+                        "com.android.cts.launchertests.support.LauncherCallbackTestsService"));
+
+        mConnection = new Connection();
+        mInstrumentation.getContext().bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+        mConnection.waitForService();
+        mResult = new Result(Looper.getMainLooper());
+        mResultMessenger = new Messenger(mResult);
+    }
+
+    public void testGetActivitiesForUserFails() throws Exception {
+        try {
+            List<LauncherActivityInfo> activities =
+                    mLauncherApps.getActivityList(null, mUser);
+            fail("getActivities for non-profile user failed to throw exception");
+        } catch (SecurityException e) {
+            // Expected.
+        }
+    }
+
+    public void testSimpleAppInstalledForUser() throws Exception {
+        List<LauncherActivityInfo> activities =
+                mLauncherApps.getActivityList(null, mUser);
+        // Check simple app is there.
+        boolean foundSimpleApp = false;
+        for (LauncherActivityInfo activity : activities) {
+            if (activity.getComponentName().getPackageName().equals(
+                    SIMPLE_APP_PACKAGE)) {
+                foundSimpleApp = true;
+            }
+            assertTrue(activity.getUser().equals(mUser));
+        }
+        assertTrue(foundSimpleApp);
+    }
+
+    public void testPackageAddedCallbackForUser() throws Throwable {
+        int result = sendMessageToCallbacksService(MSG_CHECK_PACKAGE_ADDED,
+                mUser, SIMPLE_APP_PACKAGE);
+        assertEquals(RESULT_PASS, result);
+    }
+
+    public void testPackageRemovedCallbackForUser() throws Throwable {
+        int result = sendMessageToCallbacksService(MSG_CHECK_PACKAGE_REMOVED,
+                mUser, SIMPLE_APP_PACKAGE);
+        assertEquals(RESULT_PASS, result);
+    }
+    public void testPackageChangedCallbackForUser() throws Throwable {
+        int result = sendMessageToCallbacksService(MSG_CHECK_PACKAGE_CHANGED,
+                mUser, SIMPLE_APP_PACKAGE);
+        assertEquals(RESULT_PASS, result);
+    }
+
+    public void testNoCallbackForUser() throws Throwable {
+        int result = sendMessageToCallbacksService(MSG_CHECK_NO_CALLBACK,
+                mUser, SIMPLE_APP_PACKAGE);
+        assertEquals(RESULT_PASS, result);
+    }
+
+    public void testLaunchNonExportActivityFails() throws Exception {
+        try {
+            mLauncherApps.startMainActivity(new ComponentName(
+                    SIMPLE_APP_PACKAGE,
+                    SIMPLE_APP_PACKAGE + ".NonExportedActivity"),
+                    mUser, null, null);
+            fail("starting non-exported activity failed to throw exception");
+        } catch (SecurityException e) {
+            // Expected.
+        }
+    }
+
+    public void testLaunchNonExportLauncherFails() throws Exception {
+        try {
+            mLauncherApps.startMainActivity(new ComponentName(
+                    SIMPLE_APP_PACKAGE,
+                    SIMPLE_APP_PACKAGE + ".NonLauncherActivity"),
+                    mUser, null, null);
+            fail("starting non-launcher activity failed to throw exception");
+        } catch (SecurityException e) {
+            // Expected.
+        }
+    }
+
+    public void testLaunchMainActivity() throws Exception {
+        ActivityLaunchedReceiver receiver = new ActivityLaunchedReceiver();
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(ActivityLaunchedReceiver.ACTIVITY_LAUNCHED_ACTION);
+        mInstrumentation.getContext().registerReceiver(receiver, filter);
+        mLauncherApps.startMainActivity(new ComponentName(
+                SIMPLE_APP_PACKAGE,
+                SIMPLE_APP_PACKAGE + ".SimpleActivity"),
+                mUser, null, null);
+        assertEquals(RESULT_PASS, receiver.waitForActivity());
+        mInstrumentation.getContext().unregisterReceiver(receiver);
+    }
+
+    private UserHandle getUserHandleArgument(UserManager userManager, String key,
+            Bundle arguments) throws Exception {
+        String serial = arguments.getString(key);
+        if (serial == null) {
+            return null;
+        }
+        int serialNo = Integer.parseInt(serial);
+        return userManager.getUserForSerialNumber(serialNo);
+    }
+
+    private class Connection implements ServiceConnection {
+        private final Semaphore mSemaphore = new Semaphore(0);
+
+        @Override
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            mService = new Messenger(service);
+            mSemaphore.release();
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName className) {
+            mService = null;
+        }
+
+        public void waitForService() {
+            try {
+                if (mSemaphore.tryAcquire(5, TimeUnit.SECONDS)) {
+                    return;
+                }
+            } catch (InterruptedException e) {
+            }
+            fail("failed to connec to service");
+        }
+    };
+
+    private static class Result extends Handler {
+
+        private final Semaphore mSemaphore = new Semaphore(0);
+        public int result = 0;
+
+        public Result(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.what == MSG_RESULT) {
+                result = msg.arg1;
+                mSemaphore.release();
+            } else {
+                super.handleMessage(msg);
+            }
+        }
+
+        public int waitForResult() {
+            try {
+                if (mSemaphore.tryAcquire(5, TimeUnit.SECONDS)) {
+                    return result;
+                }
+            } catch (InterruptedException e) {
+            }
+            return RESULT_TIMEOUT;
+        }
+    }
+
+    public class ActivityLaunchedReceiver extends BroadcastReceiver {
+        public static final String ACTIVITY_LAUNCHED_ACTION =
+                "com.android.cts.launchertests.LauncherAppsTests.LAUNCHED_ACTION";
+
+        private final Semaphore mSemaphore = new Semaphore(0);
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction().equals(ACTIVITY_LAUNCHED_ACTION)) {
+                mSemaphore.release();
+            }
+        }
+
+        public int waitForActivity() {
+            try {
+                if (mSemaphore.tryAcquire(5, TimeUnit.SECONDS)) {
+                    return RESULT_PASS;
+                }
+            } catch (InterruptedException e) {
+            }
+            return RESULT_TIMEOUT;
+        }
+    }
+
+    private int sendMessageToCallbacksService(int msg, UserHandle user, String packageName)
+            throws Throwable {
+        Bundle params = new Bundle();
+        params.putParcelable(USER_EXTRA, user);
+        params.putString(PACKAGE_EXTRA, packageName);
+
+        Message message = Message.obtain(null, msg, params);
+        message.replyTo = mResultMessenger;
+
+        mService.send(message);
+
+        return mResult.waitForResult();
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/TestActivity.java b/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/TestActivity.java
new file mode 100644
index 0000000..5d62c8f
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/TestActivity.java
@@ -0,0 +1,45 @@
+/*
+ * 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 com.android.cts.launchertests;
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.lang.Override;
+
+/**
+ * A simple activity to install and launch for various users to
+ * test LauncherApps.
+ */
+public class TestActivity extends Activity {
+    public static final String USER_EXTRA = "user_extra";
+    public static final int MSG_RESULT = 0;
+    public static final int RESULT_PASS = 1;
+
+    private static final String TAG = "SimpleActivity";
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        Log.i(TAG, "Created for user " + android.os.Process.myUserHandle());
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/LauncherTestsSupport/Android.mk b/hostsidetests/devicepolicy/app/LauncherTestsSupport/Android.mk
new file mode 100644
index 0000000..2465ef3
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/LauncherTestsSupport/Android.mk
@@ -0,0 +1,31 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsLauncherAppsTestsSupport
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/devicepolicy/app/LauncherTestsSupport/AndroidManifest.xml b/hostsidetests/devicepolicy/app/LauncherTestsSupport/AndroidManifest.xml
new file mode 100644
index 0000000..fbd049f
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/LauncherTestsSupport/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.launchertests.support">
+
+    <uses-sdk android:minSdkVersion="21"/>
+
+    <application>
+        <service android:name=".LauncherCallbackTestsService" >
+            <intent-filter>
+                <action android:name="com.android.cts.launchertests.support.REGISTER_CALLBACK" />
+            </intent-filter>
+        </service>
+    </application>
+</manifest>
diff --git a/hostsidetests/devicepolicy/app/LauncherTestsSupport/src/com/android/cts/launchertests/support/LauncherCallbackTestsService.java b/hostsidetests/devicepolicy/app/LauncherTestsSupport/src/com/android/cts/launchertests/support/LauncherCallbackTestsService.java
new file mode 100644
index 0000000..8d61496
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/LauncherTestsSupport/src/com/android/cts/launchertests/support/LauncherCallbackTestsService.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2014 The Android Open Sour *
+ * 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.launchertests.support;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.Pair;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Service that registers for LauncherApps callbacks.
+ *
+ * Registering in a service and different process so that
+ * device side code can launch it before running client
+ * side test code.
+ */
+public class LauncherCallbackTestsService extends Service {
+
+    public static final String USER_EXTRA = "user_extra";
+    public static final String PACKAGE_EXTRA = "package_extra";
+
+    public static final int MSG_RESULT = 0;
+    public static final int MSG_CHECK_PACKAGE_ADDED = 1;
+    public static final int MSG_CHECK_PACKAGE_REMOVED = 2;
+    public static final int MSG_CHECK_PACKAGE_CHANGED = 3;
+    public static final int MSG_CHECK_NO_CALLBACK = 4;
+
+    public static final int RESULT_PASS = 1;
+    public static final int RESULT_FAIL = 2;
+
+    private static final String TAG = "LauncherCallbackTests";
+
+    private static List<Pair<String, UserHandle>> mPackagesAdded
+            = new ArrayList<Pair<String, UserHandle>>();
+    private static List<Pair<String, UserHandle>> mPackagesRemoved
+            = new ArrayList<Pair<String, UserHandle>>();
+    private static List<Pair<String, UserHandle>> mPackagesChanged
+            = new ArrayList<Pair<String, UserHandle>>();
+    private static Object mPackagesLock = new Object();
+
+    private TestCallback mCallback;
+    private final Messenger mMessenger = new Messenger(new CheckHandler());
+
+    class CheckHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            Bundle params = null;
+            if (msg.obj instanceof Bundle) {
+                params = (Bundle) (msg.obj);
+            }
+            try {
+                switch (msg.what) {
+                    case MSG_CHECK_PACKAGE_ADDED: {
+                        boolean exists = eventExists(params, mPackagesAdded);
+                        teardown();
+                        msg.replyTo.send(Message.obtain(null, MSG_RESULT,
+                                        exists ? RESULT_PASS : RESULT_FAIL, 0));
+                        break;
+                    }
+                    case MSG_CHECK_PACKAGE_REMOVED: {
+                        boolean exists = eventExists(params, mPackagesRemoved);
+                        teardown();
+                        msg.replyTo.send(Message.obtain(null, MSG_RESULT,
+                                        exists ? RESULT_PASS : RESULT_FAIL, 0));
+                        break;
+                    }
+                    case MSG_CHECK_PACKAGE_CHANGED: {
+                        boolean exists = eventExists(params, mPackagesChanged);
+                        teardown();
+                        msg.replyTo.send(Message.obtain(null, MSG_RESULT,
+                                        exists ? RESULT_PASS : RESULT_FAIL, 0));
+                        break;
+                    }
+                    case MSG_CHECK_NO_CALLBACK: {
+                        boolean exists = eventExists(params, mPackagesAdded)
+                                || eventExists(params, mPackagesRemoved)
+                                || eventExists(params, mPackagesChanged);
+                        teardown();
+                        msg.replyTo.send(Message.obtain(null, MSG_RESULT,
+                                        exists ? RESULT_FAIL : RESULT_PASS, 0));
+                        break;
+                    }
+                    default:
+                        super.handleMessage(msg);
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to report test status");
+            }
+        }
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        if (intent == null) {
+            return START_NOT_STICKY;
+        }
+        if ("com.android.cts.launchertests.support.REGISTER_CALLBACK".equals(intent.getAction())) {
+            setup();
+        }
+        return START_STICKY;
+    }
+
+    private void setup() {
+        LauncherApps launcherApps = (LauncherApps) getSystemService(
+                Context.LAUNCHER_APPS_SERVICE);
+        synchronized (mPackagesLock) {
+            mPackagesAdded.clear();
+            mPackagesRemoved.clear();
+            mPackagesChanged.clear();
+            if (mCallback != null) {
+                launcherApps.unregisterCallback(mCallback);
+            }
+            mCallback = new TestCallback();
+            launcherApps.registerCallback(mCallback);
+        }
+    }
+
+    private void teardown() {
+        LauncherApps launcherApps = (LauncherApps) getSystemService(
+                Context.LAUNCHER_APPS_SERVICE);
+        synchronized (mPackagesLock) {
+            if (mCallback != null) {
+                launcherApps.unregisterCallback(mCallback);
+                mCallback = null;
+            }
+            mPackagesAdded.clear();
+            mPackagesRemoved.clear();
+            mPackagesChanged.clear();
+        }
+    }
+
+    private boolean eventExists(Bundle params, List<Pair<String, UserHandle>> events) {
+        UserHandle user = params.getParcelable(USER_EXTRA);
+        String packageName = params.getString(PACKAGE_EXTRA);
+        synchronized (mPackagesLock) {
+            if (events != null) {
+                for (Pair<String, UserHandle> added : events) {
+                    if (added.first.equals(packageName) && added.second.equals(user)) {
+                        Log.i(TAG, "Event exists " + packageName + " for user " + user);
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mMessenger.getBinder();
+    }
+
+    private class TestCallback extends LauncherApps.Callback {
+        public void onPackageRemoved(String packageName, UserHandle user) {
+            synchronized (mPackagesLock) {
+                mPackagesRemoved.add(new Pair<String, UserHandle>(packageName, user));
+            }
+        }
+
+        public void onPackageAdded(String packageName, UserHandle user) {
+            synchronized (mPackagesLock) {
+                mPackagesAdded.add(new Pair<String, UserHandle>(packageName, user));
+            }
+        }
+
+        public void onPackageChanged(String packageName, UserHandle user) {
+            synchronized (mPackagesLock) {
+                mPackagesChanged.add(new Pair<String, UserHandle>(packageName, user));
+            }
+        }
+
+        public void onPackagesAvailable(String[] packageNames, UserHandle user,
+                boolean replacing) {
+        }
+
+        public void onPackagesUnavailable(String[] packageNames, UserHandle user,
+                boolean replacing) {
+        }
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/SettingsIntentsTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/SettingsIntentsTest.java
new file mode 100644
index 0000000..e68cb48
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/SettingsIntentsTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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 com.android.cts.managedprofile;
+
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.test.ActivityInstrumentationTestCase2;
+import android.provider.Settings;
+
+
+/**
+ * Tests that make sure that some core application intents as described in Compatibility Definition
+ * Document are handled within a managed profile.
+ * Note that OEMs can replace the Settings apps, so we we can at most check if the intent resolves.
+ */
+public class SettingsIntentsTest extends ActivityInstrumentationTestCase2<TestActivity> {
+
+    private PackageManager mPackageManager;
+
+    public SettingsIntentsTest() {
+        super(TestActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mPackageManager = getActivity().getPackageManager();
+    }
+
+    public void testSettings() {
+        assertNotNull(mPackageManager.resolveActivity(
+                new Intent(Settings.ACTION_SETTINGS), 0 /* flags */));
+    }
+
+    public void testAccounts() {
+        assertNotNull(mPackageManager.resolveActivity(
+                new Intent(Settings.ACTION_SYNC_SETTINGS), 0 /* flags */));
+    }
+
+    public void testApps() {
+        assertNotNull(mPackageManager.resolveActivity(
+                new Intent(Settings.ACTION_APPLICATION_SETTINGS), 0 /* flags */));
+    }
+
+    public void testSecurity() {
+        // This leads to device administrators, screenlock etc.
+        assertNotNull(mPackageManager.resolveActivity(
+                new Intent(Settings.ACTION_SECURITY_SETTINGS), 0 /* flags */));
+    }
+
+    public void testNfc() {
+        if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_NFC)) {
+            assertNotNull(mPackageManager.resolveActivity(
+                    new Intent(Settings.ACTION_NFC_SETTINGS), 0 /* flags */));
+        }
+    }
+
+    public void testLocation() {
+        assertNotNull(mPackageManager.resolveActivity(
+                new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS), 0 /* flags */));
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WipeDataTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WipeDataTest.java
index b548c96..76a9e44 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WipeDataTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WipeDataTest.java
@@ -45,12 +45,6 @@
     }
 
     public void testWipeData() throws InterruptedException {
-        try {
-            mDevicePolicyManager.wipeData(DevicePolicyManager.WIPE_EXTERNAL_STORAGE);
-            fail("Should not be able to wipe external storage from managed profile.");
-        } catch (SecurityException expected) {
-        }
-
         UserHandle currentUser = Process.myUserHandle();
         assertTrue(mUserManager.getUserProfiles().contains(currentUser));
 
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/Android.mk b/hostsidetests/devicepolicy/app/SimpleApp/Android.mk
new file mode 100644
index 0000000..eae0a4f
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/SimpleApp/Android.mk
@@ -0,0 +1,31 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Don't include this package in any target.
+LOCAL_MODULE_TAGS := optional
+
+# When built, explicitly put it in the data partition.
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsSimpleApp
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml
new file mode 100644
index 0000000..848317c
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.launcherapps.simpleapp">
+
+    <application>
+        <activity android:name=".SimpleActivity" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity android:name=".NonExportedActivity">
+            android:exported="false">
+        </activity>
+        <activity android:name=".NonLauncherActivity">
+            android:exported="true">
+        </activity>
+    </application>
+
+</manifest>
+
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/NonExportedActivity.java b/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/NonExportedActivity.java
new file mode 100644
index 0000000..4801830
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/NonExportedActivity.java
@@ -0,0 +1,38 @@
+/*
+ * 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 com.android.cts.launcherapps.simpleapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.lang.Override;
+
+/**
+ * A simple activity that isn't exported, shouldn't be launchable
+ * by LauncherApps.
+ */
+public class NonExportedActivity extends Activity {
+
+    private static final String TAG = "NonExportedActivity";
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        Log.i(TAG, "Created for user " + android.os.Process.myUserHandle());
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/NonLauncherActivity.java b/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/NonLauncherActivity.java
new file mode 100644
index 0000000..4809020
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/NonLauncherActivity.java
@@ -0,0 +1,38 @@
+/*
+ * 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 com.android.cts.launcherapps.simpleapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.lang.Override;
+
+/**
+ * A simple activity that isn't main / category launcher, shouldn't be launchable
+ * by LauncherApps.
+ */
+public class NonLauncherActivity extends Activity {
+
+    private static final String TAG = "NonLauncherActivity";
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        Log.i(TAG, "Created for user " + android.os.Process.myUserHandle());
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleActivity.java b/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleActivity.java
new file mode 100644
index 0000000..1d30335
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleActivity.java
@@ -0,0 +1,47 @@
+/*
+ * 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 com.android.cts.launcherapps.simpleapp;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.lang.Override;
+
+/**
+ * A simple activity to install for various users to test LauncherApps.
+ */
+public class SimpleActivity extends Activity {
+    public static String ACTIVITY_LAUNCHED_ACTION =
+            "com.android.cts.launchertests.LauncherAppsTests.LAUNCHED_ACTION";
+
+    private static final String TAG = "SimpleActivity";
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        Log.i(TAG, "Created for user " + android.os.Process.myUserHandle());
+    }
+
+    public void onStart() {
+        super.onStart();
+        Intent reply = new Intent();
+        reply.setAction(ACTIVITY_LAUNCHED_ACTION);
+        sendBroadcast(reply);
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
index 544ddff..5149a74 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
@@ -47,14 +47,16 @@
  */
 public class BaseDevicePolicyTest extends DeviceTestCase implements IBuildReceiver {
 
-    private static final String RUNNER = "android.test.InstrumentationTestRunner";
+    protected static final String MANAGED_PROFILE_PKG = "com.android.cts.managedprofile";
+    protected static final String MANAGED_PROFILE_APK = "CtsManagedProfileApp.apk";
+    protected static final String ADMIN_RECEIVER_TEST_CLASS =
+            MANAGED_PROFILE_PKG + ".BaseManagedProfileTest$BasicAdminReceiver";
 
-    private static final String[] REQUIRED_DEVICE_FEATURES = new String[] {
-        "android.software.managed_users",
-        "android.software.device_admin" };
+    private static final String RUNNER = "android.test.InstrumentationTestRunner";
 
     private CtsBuildHelper mCtsBuild;
 
+    private HashSet<String> mAvailableFeatures;
     protected boolean mHasFeature;
 
     @Override
@@ -67,11 +69,12 @@
         super.setUp();
         assertNotNull(mCtsBuild);  // ensure build has been set before test is run.
         mHasFeature = getDevice().getApiLevel() >= 21 /* Build.VERSION_CODES.L */
-                && hasDeviceFeatures(REQUIRED_DEVICE_FEATURES);
+                && hasDeviceFeature("android.software.device_admin");
     }
 
     protected void installApp(String fileName)
             throws FileNotFoundException, DeviceNotAvailableException {
+        CLog.logAndDisplay(LogLevel.INFO, "Installing app " + fileName);
         String installResult = getDevice().installPackage(mCtsBuild.getTestApp(fileName), true);
         assertNull(String.format("Failed to install %s, Reason: %s", fileName, installResult),
                 installResult);
@@ -93,12 +96,15 @@
     }
 
     /** Initializes the user with the given id. This is required so that apps can run on it. */
-    protected void startUser(int userId) throws DeviceNotAvailableException {
+    protected void startUser(int userId) throws Exception {
         String command = "am start-user " + userId;
+        CLog.logAndDisplay(LogLevel.INFO, "Starting command " + command);
         String commandOutput = getDevice().executeShellCommand(command);
         CLog.logAndDisplay(LogLevel.INFO, "Output for command " + command + ": " + commandOutput);
         assertTrue(commandOutput + " expected to start with \"Success:\"",
                 commandOutput.startsWith("Success:"));
+        // Wait 60 seconds for intents generated to be handled.
+        Thread.sleep(60 * 1000);
     }
 
     protected int getMaxNumberOfUsersSupported() throws DeviceNotAvailableException {
@@ -137,10 +143,13 @@
         return users;
     }
 
-    protected void removeUser(int userId) throws DeviceNotAvailableException  {
+    protected void removeUser(int userId) throws Exception  {
         String removeUserCommand = "pm remove-user " + userId;
+        CLog.logAndDisplay(LogLevel.INFO, "starting command " + removeUserCommand);
         CLog.logAndDisplay(LogLevel.INFO, "Output for command " + removeUserCommand + ": "
                 + getDevice().executeShellCommand(removeUserCommand));
+        // Wait 60 seconds for user to finish being removed.
+        Thread.sleep(60 * 1000);
     }
 
     /** Returns true if the specified tests passed. Tests are run as user owner. */
@@ -166,12 +175,19 @@
         return runDeviceTests(pkgName, testClassName, testMethodName, userId);
     }
 
-    private boolean runDeviceTests(String pkgName, @Nullable String testClassName,
+    protected boolean runDeviceTests(String pkgName, @Nullable String testClassName,
             @Nullable String testMethodName, @Nullable Integer userId)
-                    throws DeviceNotAvailableException {
-        TestRunResult runResult = (userId == null)
+            throws DeviceNotAvailableException {
+        return runDeviceTests(pkgName, testClassName, testMethodName, userId, /*params*/ null);
+    }
+
+    protected boolean runDeviceTests(String pkgName, @Nullable String testClassName,
+            @Nullable String testMethodName, @Nullable Integer userId, @Nullable String params)
+                   throws DeviceNotAvailableException {
+        TestRunResult runResult = (userId == null && params == null)
                 ? doRunTests(pkgName, testClassName, testMethodName)
-                : doRunTestsAsUser(pkgName, testClassName, testMethodName, userId);
+                : doRunTestsAsUser(pkgName, testClassName, testMethodName,
+                        userId != null ? userId : 0, params != null ? params : "");
         printTestResult(runResult);
         return !runResult.hasFailedTests() && runResult.getNumTestsInState(TestStatus.PASSED) > 0;
     }
@@ -194,7 +210,7 @@
     }
 
     private TestRunResult doRunTestsAsUser(String pkgName, @Nullable String testClassName,
-            @Nullable String testMethodName, int userId)
+            @Nullable String testMethodName, int userId, String params)
             throws DeviceNotAvailableException {
         // TODO: move this to RemoteAndroidTestRunner once it supports users. Should be straight
         // forward to add a RemoteAndroidTestRunner.setUser(userId) method. Then we can merge both
@@ -206,8 +222,8 @@
                 testsToRun.append("#" + testMethodName);
             }
         }
-        String command = "am instrument --user " + userId + " -w -r " + testsToRun + " "
-                + pkgName + "/" + RUNNER;
+        String command = "am instrument --user " + userId + " " + params + " -w -r "
+                + testsToRun + " " + pkgName + "/" + RUNNER;
         CLog.i("Running " + command);
 
         CollectingTestListener listener = new CollectingTestListener();
@@ -228,31 +244,88 @@
         }
     }
 
-    private boolean hasDeviceFeatures(String[] requiredFeatures)
-            throws DeviceNotAvailableException {
-        // TODO: Move this logic to ITestDevice.
-        String command = "pm list features";
-        String commandOutput = getDevice().executeShellCommand(command);
-        CLog.i("Output for command " + command + ": " + commandOutput);
+    protected boolean hasDeviceFeature(String requiredFeature) throws DeviceNotAvailableException {
+        if (mAvailableFeatures == null) {
+            // TODO: Move this logic to ITestDevice.
+            String command = "pm list features";
+            String commandOutput = getDevice().executeShellCommand(command);
+            CLog.i("Output for command " + command + ": " + commandOutput);
 
-        // Extract the id of the new user.
-        HashSet<String> availableFeatures = new HashSet<String>();
-        for (String feature: commandOutput.split("\\s+")) {
-            // Each line in the output of the command has the format "feature:{FEATURE_VALUE}".
-            String[] tokens = feature.split(":");
-            assertTrue("\"" + feature + "\" expected to have format feature:{FEATURE_VALUE}",
-                    tokens.length > 1);
-            assertEquals(feature, "feature", tokens[0]);
-            availableFeatures.add(tokens[1]);
-        }
-
-        for (String requiredFeature : requiredFeatures) {
-            if(!availableFeatures.contains(requiredFeature)) {
-                CLog.logAndDisplay(LogLevel.INFO, "Device doesn't have required feature "
-                        + requiredFeature + ". Tests won't run.");
-                return false;
+            // Extract the id of the new user.
+            mAvailableFeatures = new HashSet<>();
+            for (String feature: commandOutput.split("\\s+")) {
+                // Each line in the output of the command has the format "feature:{FEATURE_VALUE}".
+                String[] tokens = feature.split(":");
+                assertTrue("\"" + feature + "\" expected to have format feature:{FEATURE_VALUE}",
+                        tokens.length > 1);
+                assertEquals(feature, "feature", tokens[0]);
+                mAvailableFeatures.add(tokens[1]);
             }
         }
-        return true;
+        boolean result = mAvailableFeatures.contains(requiredFeature);
+        if (!result) {
+            CLog.logAndDisplay(LogLevel.INFO, "Device doesn't have required feature "
+            + requiredFeature + ". Test won't run.");
+        }
+        return result;
+    }
+
+    protected int createUser() throws Exception {
+        String command ="pm create-user TestUser_"+ System.currentTimeMillis();
+        CLog.logAndDisplay(LogLevel.INFO, "Starting command " + command);
+        String commandOutput = getDevice().executeShellCommand(command);
+        CLog.logAndDisplay(LogLevel.INFO, "Output for command " + command + ": " + commandOutput);
+
+        // Extract the id of the new user.
+        String[] tokens = commandOutput.split("\\s+");
+        assertTrue(tokens.length > 0);
+        assertEquals("Success:", tokens[0]);
+        // Wait 60 seconds for intents generated to be handled.
+        Thread.sleep(60 * 1000);
+        return Integer.parseInt(tokens[tokens.length-1]);
+    }
+
+    protected int createManagedProfile() throws DeviceNotAvailableException {
+        String command =
+                "pm create-user --profileOf 0 --managed TestProfile_" + System.currentTimeMillis();
+        CLog.logAndDisplay(LogLevel.INFO, "Starting command " + command);
+        String commandOutput = getDevice().executeShellCommand(command);
+        CLog.logAndDisplay(LogLevel.INFO, "Output for command " + command + ": " + commandOutput);
+
+        // Extract the id of the new user.
+        String[] tokens = commandOutput.split("\\s+");
+        assertTrue(commandOutput + " expected to have format \"Success: {USER_ID}\"",
+                tokens.length > 0);
+        assertEquals(commandOutput, "Success:", tokens[0]);
+        return Integer.parseInt(tokens[tokens.length-1]);
+    }
+
+
+    protected int getUserSerialNumber(int userId) throws DeviceNotAvailableException{
+        // dumpsys user return lines like "UserInfo{0:Owner:13} serialNo=0"
+        String commandOutput = getDevice().executeShellCommand("dumpsys user");
+        String[] tokens = commandOutput.split("\\n");
+        for (String token : tokens) {
+            token = token.trim();
+            if (token.contains("UserInfo{" + userId + ":")) {
+                String[] split = token.split("serialNo=");
+                assertTrue(split.length == 2);
+                int serialNumber = Integer.parseInt(split[1]);
+                CLog.logAndDisplay(LogLevel.INFO, "Serial number of user " + userId + ": "
+                        + serialNumber);
+                return serialNumber;
+            }
+        }
+        fail("Couldn't find user " + userId);
+        return -1;
+    }
+
+    protected void setProfileOwner(String componentName, int userId)
+            throws DeviceNotAvailableException {
+        String command = "dpm set-profile-owner '" + componentName + "' " + userId;
+        String commandOutput = getDevice().executeShellCommand(command);
+        CLog.logAndDisplay(LogLevel.INFO, "Output for command " + command + ": " + commandOutput);
+        assertTrue(commandOutput + " expected to start with \"Success:\"",
+                commandOutput.startsWith("Success:"));
     }
 }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseLauncherAppsTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseLauncherAppsTest.java
new file mode 100644
index 0000000..dec8bd2
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseLauncherAppsTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.cts.devicepolicy;
+
+import com.android.ddmlib.Log.LogLevel;
+import com.android.tradefed.log.LogUtil.CLog;
+
+/**
+ * Common code for the various LauncherApps tests.
+ */
+public class BaseLauncherAppsTest extends BaseDevicePolicyTest {
+
+    protected static final String SIMPLE_APP_PKG = "com.android.cts.launcherapps.simpleapp";
+    protected static final String SIMPLE_APP_APK = "CtsSimpleApp.apk";
+    protected static final String LAUNCHER_TESTS_PKG = "com.android.cts.launchertests";
+    protected static final String LAUNCHER_TESTS_CLASS = LAUNCHER_TESTS_PKG + ".LauncherAppsTests";
+    private static final String LAUNCHER_TESTS_APK = "CtsLauncherAppsTests.apk";
+    private static final String LAUNCHER_TESTS_SUPPORT_PKG =
+            "com.android.cts.launchertests.support";
+    private static final String LAUNCHER_TESTS_SUPPORT_APK = "CtsLauncherAppsTestsSupport.apk";
+
+    protected void installTestApps() throws Exception {
+        uninstallTestApps();
+        installApp(LAUNCHER_TESTS_APK);
+        installApp(LAUNCHER_TESTS_SUPPORT_APK);
+    }
+
+    protected void uninstallTestApps() throws Exception {
+        getDevice().uninstallPackage(LAUNCHER_TESTS_PKG);
+        getDevice().uninstallPackage(LAUNCHER_TESTS_SUPPORT_PKG);
+        getDevice().uninstallPackage(SIMPLE_APP_PKG);
+    }
+
+    protected void removeTestUsers() throws Exception {
+        for (int userId : listUsers()) {
+            if (userId != 0) {
+                removeUser(userId);
+            }
+        }
+    }
+
+    protected void startCallbackService() throws Exception {
+        String command = "am startservice --user 0 "
+                + "-a " + LAUNCHER_TESTS_SUPPORT_PKG + ".REGISTER_CALLBACK "
+                + LAUNCHER_TESTS_SUPPORT_PKG + "/.LauncherCallbackTestsService";
+        CLog.logAndDisplay(LogLevel.INFO, "Output for command " + command + ": "
+              + getDevice().executeShellCommand(command));
+
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsMultiUserTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsMultiUserTest.java
new file mode 100644
index 0000000..0af38a4
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsMultiUserTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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 com.android.cts.devicepolicy;
+
+import com.android.ddmlib.Log.LogLevel;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
+
+/**
+ * Set of tests for LauncherApps attempting to access a non-profiles
+ * apps.
+ */
+public class LauncherAppsMultiUserTest extends BaseLauncherAppsTest {
+
+    private int mSecondaryUserId;
+    private int mSecondaryUserSerialNumber;
+
+    private boolean mMultiUserSupported;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        // We need multi user to be supported in order to create a secondary user
+        // and api level 21 to support LauncherApps
+        mMultiUserSupported = getMaxNumberOfUsersSupported() > 1 && getDevice().getApiLevel() >= 21;
+
+        if (mMultiUserSupported) {
+            removeTestUsers();
+            installTestApps();
+            // Create a secondary user.
+            mSecondaryUserId = createUser();
+            mSecondaryUserSerialNumber = getUserSerialNumber(mSecondaryUserId);
+            startUser(mSecondaryUserId);
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (mMultiUserSupported) {
+            removeUser(mSecondaryUserId);
+            uninstallTestApps();
+        }
+        super.tearDown();
+    }
+
+    public void testGetActivitiesForNonProfileFails() throws Exception {
+        if (!mMultiUserSupported) {
+            return;
+        }
+        installApp(SIMPLE_APP_APK);
+        try {
+            assertTrue(runDeviceTests(LAUNCHER_TESTS_PKG,
+                    LAUNCHER_TESTS_CLASS,
+                    "testGetActivitiesForUserFails",
+                            0, "-e testUser " + mSecondaryUserSerialNumber));
+        } finally {
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+        }
+    }
+
+    public void testNoLauncherCallbackPackageAddedSecondaryUser() throws Exception {
+        if (!mMultiUserSupported) {
+            return;
+        }
+        startCallbackService();
+        installApp(SIMPLE_APP_APK);
+        try {
+            assertTrue(runDeviceTests(LAUNCHER_TESTS_PKG,
+                    LAUNCHER_TESTS_CLASS,
+                            "testNoCallbackForUser",
+                            0, "-e testUser " + mSecondaryUserSerialNumber));
+        } finally {
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+        }
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsProfileTest.java
new file mode 100644
index 0000000..f8c2e7d
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsProfileTest.java
@@ -0,0 +1,131 @@
+/*
+ * 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 com.android.cts.devicepolicy;
+
+import com.android.ddmlib.Log.LogLevel;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
+
+/**
+ * Set of tests for LauncherApps with managed profiles.
+ */
+public class LauncherAppsProfileTest extends BaseLauncherAppsTest {
+
+    private int mProfileUserId;
+    private int mProfileSerialNumber;
+    private int mMainUserSerialNumber;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        // We need multi user to be supported in order to create a profile of the user owner.
+        mHasFeature = mHasFeature && (getMaxNumberOfUsersSupported() > 1);
+
+        if (mHasFeature) {
+            removeTestUsers();
+            installTestApps();
+            // Create a managed profile
+            mProfileUserId = createManagedProfile();
+            installApp(MANAGED_PROFILE_APK);
+            setProfileOwner(MANAGED_PROFILE_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mProfileUserId);
+            mProfileSerialNumber = getUserSerialNumber(mProfileUserId);
+            mMainUserSerialNumber = getUserSerialNumber(0);
+            startUser(mProfileUserId);
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (mHasFeature) {
+            removeUser(mProfileUserId);
+            uninstallTestApps();
+            getDevice().uninstallPackage(MANAGED_PROFILE_PKG);
+        }
+        super.tearDown();
+    }
+
+    public void testGetActivitiesWithProfile() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        // Install app for all users.
+        installApp(SIMPLE_APP_APK);
+        try {
+            // Run tests to check SimpleApp exists in both profile and main user.
+            assertTrue(runDeviceTests(LAUNCHER_TESTS_PKG,
+                    LAUNCHER_TESTS_CLASS,
+                    "testSimpleAppInstalledForUser",
+                            0, "-e testUser " + mProfileSerialNumber));
+            assertTrue(runDeviceTests(LAUNCHER_TESTS_PKG,
+                    LAUNCHER_TESTS_PKG + ".LauncherAppsTests", "testSimpleAppInstalledForUser",
+                            0, "-e testUser " + mMainUserSerialNumber));
+        } finally {
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+        }
+    }
+
+    public void testLauncherCallbackPackageAddedProfile() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        startCallbackService();
+        installApp(SIMPLE_APP_APK);
+        try {
+            assertTrue(runDeviceTests(LAUNCHER_TESTS_PKG,
+                    LAUNCHER_TESTS_CLASS,
+                            "testPackageAddedCallbackForUser",
+                            0, "-e testUser " + mProfileSerialNumber));
+        } finally {
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+        }
+    }
+
+    public void testLauncherCallbackPackageRemovedProfile() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        installApp(SIMPLE_APP_APK);
+        try {
+            startCallbackService();
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+            assertTrue(runDeviceTests(LAUNCHER_TESTS_PKG,
+                    LAUNCHER_TESTS_CLASS,
+                            "testPackageRemovedCallbackForUser",
+                            0, "-e testUser " + mProfileSerialNumber));
+        } finally {
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+        }
+    }
+
+    public void testLauncherCallbackPackageChangedProfile() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        installApp(SIMPLE_APP_APK);
+        try {
+            startCallbackService();
+            installApp(SIMPLE_APP_APK);
+            assertTrue(runDeviceTests(LAUNCHER_TESTS_PKG,
+                    LAUNCHER_TESTS_CLASS,
+                            "testPackageChangedCallbackForUser",
+                            0, "-e testUser " + mProfileSerialNumber));
+        } finally {
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+        }
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsSingleUserTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsSingleUserTest.java
new file mode 100644
index 0000000..32be962
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsSingleUserTest.java
@@ -0,0 +1,160 @@
+/*
+ * 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 com.android.cts.devicepolicy;
+
+import com.android.ddmlib.Log.LogLevel;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
+
+/**
+ * Set of tests for LauncherApps with managed profiles.
+ */
+public class LauncherAppsSingleUserTest extends BaseLauncherAppsTest {
+
+    private boolean mHasLauncherApps;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mHasLauncherApps = getDevice().getApiLevel() >= 21;
+
+        if (mHasLauncherApps) {
+            installTestApps();
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (mHasLauncherApps) {
+            uninstallTestApps();
+        }
+        super.tearDown();
+    }
+
+    public void testInstallAppMainUser() throws Exception {
+        if (!mHasLauncherApps) {
+            return;
+        }
+        installApp(SIMPLE_APP_APK);
+        try {
+            int serialNumber = getUserSerialNumber(0);
+            assertTrue(runDeviceTests(LAUNCHER_TESTS_PKG,
+                    LAUNCHER_TESTS_CLASS, "testSimpleAppInstalledForUser",
+                            0, "-e testUser " + serialNumber));
+        } finally {
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+        }
+    }
+
+    public void testLauncherCallbackPackageAddedMainUser() throws Exception {
+        if (!mHasLauncherApps) {
+            return;
+        }
+        startCallbackService();
+        installApp(SIMPLE_APP_APK);
+        try {
+            int serialNumber = getUserSerialNumber(0);
+            assertTrue(runDeviceTests(LAUNCHER_TESTS_PKG,
+                    LAUNCHER_TESTS_CLASS,
+                            "testPackageAddedCallbackForUser",
+                            0, "-e testUser " + serialNumber));
+        } finally {
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+        }
+    }
+
+    public void testLauncherCallbackPackageRemovedMainUser() throws Exception {
+        if (!mHasLauncherApps) {
+            return;
+        }
+        installApp(SIMPLE_APP_APK);
+        try {
+            startCallbackService();
+            int serialNumber = getUserSerialNumber(0);
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+            assertTrue(runDeviceTests(LAUNCHER_TESTS_PKG,
+                    LAUNCHER_TESTS_CLASS,
+                            "testPackageRemovedCallbackForUser",
+                            0, "-e testUser " + serialNumber));
+        } finally {
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+        }
+    }
+
+    public void testLauncherCallbackPackageChangedMainUser() throws Exception {
+        if (!mHasLauncherApps) {
+            return;
+        }
+        installApp(SIMPLE_APP_APK);
+        try {
+            startCallbackService();
+            int serialNumber = getUserSerialNumber(0);
+            installApp(SIMPLE_APP_APK);
+            assertTrue(runDeviceTests(LAUNCHER_TESTS_PKG,
+                    LAUNCHER_TESTS_CLASS,
+                            "testPackageChangedCallbackForUser",
+                            0, "-e testUser " + serialNumber));
+        } finally {
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+        }
+    }
+
+    public void testLauncherNonExportedAppFails() throws Exception {
+        if (!mHasLauncherApps) {
+            return;
+        }
+        installApp(SIMPLE_APP_APK);
+        try {
+            int serialNumber = getUserSerialNumber(0);
+            assertTrue(runDeviceTests(LAUNCHER_TESTS_PKG,
+                    LAUNCHER_TESTS_CLASS, "testLaunchNonExportActivityFails",
+                            0, "-e testUser " + serialNumber));
+        } finally {
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+        }
+    }
+
+    public void testLaunchNonExportActivityFails() throws Exception {
+        if (!mHasLauncherApps) {
+            return;
+        }
+        installApp(SIMPLE_APP_APK);
+        try {
+            int serialNumber = getUserSerialNumber(0);
+            assertTrue(runDeviceTests(LAUNCHER_TESTS_PKG,
+                    LAUNCHER_TESTS_CLASS, "testLaunchNonExportLauncherFails",
+                            0, "-e testUser " + serialNumber));
+        } finally {
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+        }
+    }
+
+    public void testLaunchMainActivity() throws Exception {
+        if (!mHasLauncherApps) {
+            return;
+        }
+        installApp(SIMPLE_APP_APK);
+        try {
+            int serialNumber = getUserSerialNumber(0);
+            assertTrue(runDeviceTests(LAUNCHER_TESTS_PKG,
+                    LAUNCHER_TESTS_CLASS, "testLaunchMainActivity",
+                            0, "-e testUser " + serialNumber));
+        } finally {
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+        }
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
index 6ece85c..7da4228 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
@@ -44,7 +44,8 @@
         super.setUp();
 
         // We need multi user to be supported in order to create a profile of the user owner.
-        mHasFeature = mHasFeature && (getMaxNumberOfUsersSupported() > 1);
+        mHasFeature = mHasFeature && (getMaxNumberOfUsersSupported() > 1) && hasDeviceFeature(
+                "android.software.managed_users");
 
         if (mHasFeature) {
             mUserId = createManagedProfile();
@@ -87,6 +88,14 @@
         assertFalse(listUsers().contains(mUserId));
     }
 
+    public void testMaxUsersStrictlyMoreThanOne() throws Exception {
+        if (hasDeviceFeature("android.software.managed_users")) {
+            assertTrue("Device must support more than 1 user "
+                    + "if android.software.managed_users feature is available",
+            getMaxNumberOfUsersSupported() > 1);
+        }
+    }
+
     public void testCrossProfileIntentFilters() throws Exception {
         if (!mHasFeature) {
             return;
@@ -106,7 +115,14 @@
               + getDevice().executeShellCommand(command));
         assertTrue(runDeviceTests(MANAGED_PROFILE_PKG, MANAGED_PROFILE_PKG + ".PrimaryUserTest"));
         // TODO: Test with startActivity
-        // TODO: Test with CtsVerifier for disambiguation cases
+    }
+
+    public void testSettingsIntents() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+
+        assertTrue(runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".SettingsIntentsTest", mUserId));
     }
 
     public void testCrossProfileContent() throws Exception {
@@ -189,27 +205,4 @@
                 "Output for command " + adbCommand + ": " + commandOutput);
         return commandOutput;
     }
-
-    private int createManagedProfile() throws DeviceNotAvailableException {
-        String command =
-                "pm create-user --profileOf 0 --managed TestProfile_" + System.currentTimeMillis();
-        String commandOutput = getDevice().executeShellCommand(command);
-        CLog.logAndDisplay(LogLevel.INFO, "Output for command " + command + ": " + commandOutput);
-
-        // Extract the id of the new user.
-        String[] tokens = commandOutput.split("\\s+");
-        assertTrue(commandOutput + " expected to have format \"Success: {USER_ID}\"",
-                tokens.length > 0);
-        assertEquals(commandOutput, "Success:", tokens[0]);
-        return Integer.parseInt(tokens[tokens.length-1]);
-    }
-
-    private void setProfileOwner(String componentName, int userId)
-            throws DeviceNotAvailableException {
-        String command = "dpm set-profile-owner '" + componentName + "' " + userId;
-        String commandOutput = getDevice().executeShellCommand(command);
-        CLog.logAndDisplay(LogLevel.INFO, "Output for command " + command + ": " + commandOutput);
-        assertTrue(commandOutput + " expected to start with \"Success:\"",
-                commandOutput.startsWith("Success:"));
-    }
 }
diff --git a/hostsidetests/dumpsys/Android.mk b/hostsidetests/dumpsys/Android.mk
new file mode 100644
index 0000000..51ea31f
--- /dev/null
+++ b/hostsidetests/dumpsys/Android.mk
@@ -0,0 +1,32 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE_TAGS := optional
+
+# Must match the package name in CtsTestCaseList.mk
+LOCAL_MODULE := CtsDumpsysHostTestCases
+
+LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed-prebuilt
+
+LOCAL_CTS_TEST_PACKAGE := android.host.dumpsys
+
+include $(BUILD_CTS_HOST_JAVA_LIBRARY)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/dumpsys/src/android/dumpsys/cts/DumpsysHostTest.java b/hostsidetests/dumpsys/src/android/dumpsys/cts/DumpsysHostTest.java
new file mode 100644
index 0000000..4668faf
--- /dev/null
+++ b/hostsidetests/dumpsys/src/android/dumpsys/cts/DumpsysHostTest.java
@@ -0,0 +1,835 @@
+/*
+ * 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.dumpsys.cts;
+
+import com.android.ddmlib.Log;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceTestCase;
+
+import java.io.BufferedReader;
+import java.io.StringReader;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Test to check the format of the dumps of various services (currently only procstats is tested).
+ */
+public class DumpsysHostTest extends DeviceTestCase {
+    private static final String TAG = "DumpsysHostTest";
+
+    /**
+     * A reference to the device under test.
+     */
+    private ITestDevice mDevice;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mDevice = getDevice();
+    }
+
+    /**
+     * Tests the output of "dumpsys procstats -c". This is a proxy for testing "dumpsys procstats
+     * --checkin", since the latter is not idempotent.
+     *
+     * @throws Exception
+     */
+    public void testProcstatsOutput() throws Exception {
+        if (mDevice.getApiLevel() < 19) {
+            Log.i(TAG, "No Procstats output before KitKat, skipping test.");
+            return;
+        }
+
+        String procstats = mDevice.executeShellCommand("dumpsys procstats -c");
+        assertNotNull(procstats);
+        assertTrue(procstats.length() > 0);
+
+        Set<String> seenTags = new HashSet<>();
+        int version = -1;
+
+        try (BufferedReader reader = new BufferedReader(
+                new StringReader(procstats))) {
+
+            String line;
+            while ((line = reader.readLine()) != null) {
+                if (line.isEmpty()) {
+                    continue;
+                }
+
+                // extra space to make sure last column shows up.
+                if (line.endsWith(",")) {
+                  line = line + " ";
+                }
+                String[] parts = line.split(",");
+                seenTags.add(parts[0]);
+
+                switch (parts[0]) {
+                    case "vers":
+                        assertEquals(2, parts.length);
+                        version = Integer.parseInt(parts[1]);
+                        break;
+                    case "period":
+                        checkPeriod(parts);
+                        break;
+                    case "pkgproc":
+                        checkPkgProc(parts, version);
+                        break;
+                    case "pkgpss":
+                        checkPkgPss(parts, version);
+                        break;
+                    case "pkgsvc-bound":
+                    case "pkgsvc-exec":
+                    case "pkgsvc-run":
+                    case "pkgsvc-start":
+                        checkPkgSvc(parts, version);
+                        break;
+                    case "pkgkills":
+                        checkPkgKills(parts, version);
+                        break;
+                    case "proc":
+                        checkProc(parts);
+                        break;
+                    case "pss":
+                        checkPss(parts);
+                        break;
+                    case "kills":
+                        checkKills(parts);
+                        break;
+                    case "total":
+                        checkTotal(parts);
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+
+        // spot check a few tags
+        assertSeenTag(seenTags, "pkgproc");
+        assertSeenTag(seenTags, "proc");
+        assertSeenTag(seenTags, "pss");
+        assertSeenTag(seenTags, "total");
+    }
+
+    private void checkPeriod(String[] parts) {
+        assertEquals(5, parts.length);
+        assertNotNull(parts[1]); // date
+        assertInteger(parts[2]); // start time (msec)
+        assertInteger(parts[3]); // end time (msec)
+        assertNotNull(parts[4]); // status
+    }
+
+    private void checkPkgProc(String[] parts, int version) {
+        int statesStartIndex;
+
+        if (version < 4) {
+            assertTrue(parts.length >= 4);
+            assertNotNull(parts[1]); // package name
+            assertInteger(parts[2]); // uid
+            assertNotNull(parts[3]); // process
+            statesStartIndex = 4;
+        } else {
+            assertTrue(parts.length >= 5);
+            assertNotNull(parts[1]); // package name
+            assertInteger(parts[2]); // uid
+            assertInteger(parts[3]); // app version
+            assertNotNull(parts[4]); // process
+            statesStartIndex = 5;
+        }
+
+        for (int i = statesStartIndex; i < parts.length; i++) {
+            String[] subparts = parts[i].split(":");
+            assertEquals(2, subparts.length);
+            checkTag(subparts[0], true); // tag
+            assertInteger(subparts[1]); // duration (msec)
+        }
+    }
+
+    private void checkTag(String tag, boolean hasProcess) {
+        assertEquals(hasProcess ? 3 : 2, tag.length());
+
+        // screen: 0 = off, 1 = on
+        char s = tag.charAt(0);
+        if (s != '0' && s != '1') {
+            fail("malformed tag: " + tag);
+        }
+
+        // memory: n = normal, m = moderate, l = low, c = critical
+        char m = tag.charAt(1);
+        if (m != 'n' && m != 'm' && m != 'l' && m != 'c') {
+            fail("malformed tag: " + tag);
+        }
+
+        if (hasProcess) {
+            char p = tag.charAt(2);
+            assertTrue("malformed tag: " + tag, p >= 'a' && p <= 'z');
+        }
+    }
+
+    private void checkPkgPss(String[] parts, int version) {
+        int statesStartIndex;
+
+        if (version < 4) {
+            assertTrue(parts.length >= 4);
+            assertNotNull(parts[1]); // package name
+            assertInteger(parts[2]); // uid
+            assertNotNull(parts[3]); // process
+            statesStartIndex = 4;
+        } else {
+            assertTrue(parts.length >= 5);
+            assertNotNull(parts[1]); // package name
+            assertInteger(parts[2]); // uid
+            assertInteger(parts[3]); // app version
+            assertNotNull(parts[4]); // process
+            statesStartIndex = 5;
+        }
+
+        for (int i = statesStartIndex; i < parts.length; i++) {
+            String[] subparts = parts[i].split(":");
+            assertEquals(8, subparts.length);
+            checkTag(subparts[0], true); // tag
+            assertInteger(subparts[1]); // sample size
+            assertInteger(subparts[2]); // pss min
+            assertInteger(subparts[3]); // pss avg
+            assertInteger(subparts[4]); // pss max
+            assertInteger(subparts[5]); // uss min
+            assertInteger(subparts[6]); // uss avg
+            assertInteger(subparts[7]); // uss max
+        }
+    }
+
+    private void checkPkgSvc(String[] parts, int version) {
+        int statesStartIndex;
+
+        if (version < 4) {
+            assertTrue(parts.length >= 5);
+            assertNotNull(parts[1]); // package name
+            assertInteger(parts[2]); // uid
+            assertNotNull(parts[3]); // service name
+            assertInteger(parts[4]); // count
+            statesStartIndex = 5;
+        } else {
+            assertTrue(parts.length >= 6);
+            assertNotNull(parts[1]); // package name
+            assertInteger(parts[2]); // uid
+            assertInteger(parts[3]); // app version
+            assertNotNull(parts[4]); // service name
+            assertInteger(parts[5]); // count
+            statesStartIndex = 6;
+        }
+
+        for (int i = statesStartIndex; i < parts.length; i++) {
+            String[] subparts = parts[i].split(":");
+            assertEquals(2, subparts.length);
+            checkTag(subparts[0], false); // tag
+            assertInteger(subparts[1]); // duration (msec)
+        }
+    }
+
+    private void checkPkgKills(String[] parts, int version) {
+        String pssStr;
+
+        if (version < 4) {
+            assertEquals(8, parts.length);
+            assertNotNull(parts[1]); // package name
+            assertInteger(parts[2]); // uid
+            assertNotNull(parts[3]); // process
+            assertInteger(parts[4]); // wakes
+            assertInteger(parts[5]); // cpu
+            assertInteger(parts[6]); // cached
+            pssStr = parts[7];
+        } else {
+            assertEquals(9, parts.length);
+            assertNotNull(parts[1]); // package name
+            assertInteger(parts[2]); // uid
+            assertInteger(parts[3]); // app version
+            assertNotNull(parts[4]); // process
+            assertInteger(parts[5]); // wakes
+            assertInteger(parts[6]); // cpu
+            assertInteger(parts[7]); // cached
+            pssStr = parts[8];
+        }
+
+        String[] subparts = pssStr.split(":");
+        assertEquals(3, subparts.length);
+        assertInteger(subparts[0]); // pss min
+        assertInteger(subparts[1]); // pss avg
+        assertInteger(subparts[2]); // pss max
+    }
+
+    private void checkProc(String[] parts) {
+        assertTrue(parts.length >= 3);
+        assertNotNull(parts[1]); // package name
+        assertInteger(parts[2]); // uid
+
+        for (int i = 3; i < parts.length; i++) {
+            String[] subparts = parts[i].split(":");
+            assertEquals(2, subparts.length);
+            checkTag(subparts[0], true); // tag
+            assertInteger(subparts[1]); // duration (msec)
+        }
+    }
+
+    private void checkPss(String[] parts) {
+        assertTrue(parts.length >= 3);
+        assertNotNull(parts[1]); // package name
+        assertInteger(parts[2]); // uid
+
+        for (int i = 3; i < parts.length; i++) {
+            String[] subparts = parts[i].split(":");
+            assertEquals(8, subparts.length);
+            checkTag(subparts[0], true); // tag
+            assertInteger(subparts[1]); // sample size
+            assertInteger(subparts[2]); // pss min
+            assertInteger(subparts[3]); // pss avg
+            assertInteger(subparts[4]); // pss max
+            assertInteger(subparts[5]); // uss min
+            assertInteger(subparts[6]); // uss avg
+            assertInteger(subparts[7]); // uss max
+        }
+    }
+
+    private void checkKills(String[] parts) {
+        assertEquals(7, parts.length);
+        assertNotNull(parts[1]); // package name
+        assertInteger(parts[2]); // uid
+        assertInteger(parts[3]); // wakes
+        assertInteger(parts[4]); // cpu
+        assertInteger(parts[5]); // cached
+        String pssStr = parts[6];
+
+        String[] subparts = pssStr.split(":");
+        assertEquals(3, subparts.length);
+        assertInteger(subparts[0]); // pss min
+        assertInteger(subparts[1]); // pss avg
+        assertInteger(subparts[2]); // pss max
+    }
+
+    private void checkTotal(String[] parts) {
+        assertTrue(parts.length >= 2);
+        for (int i = 1; i < parts.length; i++) {
+            String[] subparts = parts[i].split(":");
+            checkTag(subparts[0], false); // tag
+
+            if (subparts[1].contains("sysmemusage")) {
+                break; // see b/18340771
+            }
+            assertInteger(subparts[1]); // duration (msec)
+        }
+    }
+
+    /**
+     * Tests the output of "dumpsys batterystats --checkin".
+     *
+     * @throws Exception
+     */
+    public void testBatterystatsOutput() throws Exception {
+        if (mDevice.getApiLevel() < 21) {
+            Log.i(TAG, "Batterystats output before Lollipop, skipping test.");
+            return;
+        }
+
+        String batterystats = mDevice.executeShellCommand("dumpsys batterystats --checkin");
+        assertNotNull(batterystats);
+        assertTrue(batterystats.length() > 0);
+
+        Set<String> seenTags = new HashSet<>();
+        int version = -1;
+
+        try (BufferedReader reader = new BufferedReader(
+                new StringReader(batterystats))) {
+
+            String line;
+            while ((line = reader.readLine()) != null) {
+                if (line.isEmpty()) {
+                    continue;
+                }
+
+                String[] parts = line.split(",");
+                assertInteger(parts[0]); // old version
+                assertInteger(parts[1]); // UID
+                switch (parts[2]) { // aggregation type
+                    case "i":
+                    case "l":
+                    case "c":
+                    case "u":
+                        break;
+                    default:
+                        fail("malformed stat: " + parts[2]);
+                }
+                assertNotNull(parts[3]);
+                seenTags.add(parts[3]);
+
+                // Note the time fields are measured in milliseconds by default.
+                switch (parts[3]) {
+                    case "vers":
+                        checkVersion(parts);
+                        break;
+                    case "uid":
+                        checkUid(parts);
+                        break;
+                    case "apk":
+                        checkApk(parts);
+                        break;
+                    case "pr":
+                        checkProcess(parts);
+                        break;
+                    case "sr":
+                        checkSensor(parts);
+                        break;
+                    case "vib":
+                        checkVibrator(parts);
+                        break;
+                    case "fg":
+                        checkForeground(parts);
+                        break;
+                    case "st":
+                        checkStateTime(parts);
+                        break;
+                    case "wl":
+                        checkWakelock(parts);
+                        break;
+                    case "sy":
+                        checkSync(parts);
+                        break;
+                    case "jb":
+                        checkJob(parts);
+                        break;
+                    case "kwl":
+                        checkKernelWakelock(parts);
+                        break;
+                    case "wr":
+                        checkWakeupReason(parts);
+                        break;
+                    case "nt":
+                        checkNetwork(parts);
+                        break;
+                    case "ua":
+                        checkUserActivity(parts);
+                        break;
+                    case "bt":
+                        checkBattery(parts);
+                        break;
+                    case "dc":
+                        checkBatteryDischarge(parts);
+                        break;
+                    case "lv":
+                        checkBatteryLevel(parts);
+                        break;
+                    case "wfl":
+                        checkWifi(parts);
+                        break;
+                    case "m":
+                        checkMisc(parts);
+                        break;
+                    case "gn":
+                        checkGlobalNetwork(parts);
+                        break;
+                    case "br":
+                        checkScreenBrightness(parts);
+                        break;
+                    case "sgt":
+                    case "sgc":
+                        checkSignalStrength(parts);
+                        break;
+                    case "sst":
+                        checkSignalScanningTime(parts);
+                        break;
+                    case "dct":
+                    case "dcc":
+                        checkDataConnection(parts);
+                        break;
+                    case "wst":
+                    case "wsc":
+                        checkWifiState(parts);
+                        break;
+                    case "wsst":
+                    case "wssc":
+                        checkWifiSupplState(parts);
+                        break;
+                    case "wsgt":
+                    case "wsgc":
+                        checkWifiSignalStrength(parts);
+                        break;
+                    case "bst":
+                    case "bsc":
+                        checkBluetoothState(parts);
+                        break;
+                    case "pws":
+                        checkPowerUseSummary(parts);
+                        break;
+                    case "pwi":
+                        checkPowerUseItem(parts);
+                        break;
+                    case "dsd":
+                    case "csd":
+                        checkChargeDischargeStep(parts);
+                        break;
+                    case "dtr":
+                        checkDischargeTimeRemain(parts);
+                        break;
+                    case "ctr":
+                        checkChargeTimeRemain(parts);
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+
+        // spot check a few tags
+        assertSeenTag(seenTags, "vers");
+        assertSeenTag(seenTags, "bt");
+        assertSeenTag(seenTags, "dc");
+        assertSeenTag(seenTags, "m");
+    }
+
+    private void checkVersion(String[] parts) {
+        assertEquals(8, parts.length);
+        assertInteger(parts[4]); // checkinVersion
+        assertInteger(parts[5]); // parcelVersion
+        assertNotNull(parts[6]); // startPlatformVersion
+        assertNotNull(parts[7]); // endPlatformVersion
+    }
+
+    private void checkUid(String[] parts) {
+        assertEquals(6, parts.length);
+        assertInteger(parts[4]); // uid
+        assertNotNull(parts[5]); // pkgName
+    }
+
+    private void checkApk(String[] parts) {
+        assertEquals(10, parts.length);
+        assertInteger(parts[4]); // wakeups
+        assertNotNull(parts[5]); // apk
+        assertNotNull(parts[6]); // service
+        assertInteger(parts[7]); // startTime
+        assertInteger(parts[8]); // starts
+        assertInteger(parts[9]); // launches
+    }
+
+    private void checkProcess(String[] parts) {
+        assertEquals(9, parts.length);
+        assertNotNull(parts[4]); // process
+        assertInteger(parts[5]); // userMillis
+        assertInteger(parts[6]); // systemMillis
+        assertInteger(parts[7]); // foregroundMillis
+        assertInteger(parts[8]); // starts
+    }
+
+    private void checkSensor(String[] parts) {
+        assertEquals(7, parts.length);
+        assertInteger(parts[4]); // sensorNumber
+        assertInteger(parts[5]); // totalTime
+        assertInteger(parts[6]); // count
+    }
+
+    private void checkVibrator(String[] parts) {
+        assertEquals(6, parts.length);
+        assertInteger(parts[4]); // totalTime
+        assertInteger(parts[5]); // count
+    }
+
+    private void checkForeground(String[] parts) {
+        assertEquals(6, parts.length);
+        assertInteger(parts[4]); // totalTime
+        assertInteger(parts[5]); // count
+    }
+
+    private void checkStateTime(String[] parts) {
+        assertEquals(7, parts.length);
+        assertInteger(parts[4]); // foreground
+        assertInteger(parts[5]); // active
+        assertInteger(parts[6]); // running
+    }
+
+    private void checkWakelock(String[] parts) {
+        assertEquals(14, parts.length);
+        assertNotNull(parts[4]);      // wakelock
+        assertInteger(parts[5]);      // full totalTime
+        assertEquals("f", parts[6]);  // full
+        assertInteger(parts[7]);      // full count
+        assertInteger(parts[8]);      // partial totalTime
+        assertEquals("p", parts[9]);  // partial
+        assertInteger(parts[10]);     // partial count
+        assertInteger(parts[11]);     // window totalTime
+        assertEquals("w", parts[12]); // window
+        assertInteger(parts[13]);     // window count
+    }
+
+    private void checkSync(String[] parts) {
+        assertEquals(7, parts.length);
+        assertNotNull(parts[4]); // sync
+        assertInteger(parts[5]); // totalTime
+        assertInteger(parts[6]); // count
+    }
+
+    private void checkJob(String[] parts) {
+        assertEquals(7, parts.length);
+        assertNotNull(parts[4]); // job
+        assertInteger(parts[5]); // totalTime
+        assertInteger(parts[6]); // count
+    }
+
+    private void checkKernelWakelock(String[] parts) {
+        assertEquals(7, parts.length);
+        assertNotNull(parts[4]); // kernel wakelock
+        assertInteger(parts[5]); // totalTime
+        assertInteger(parts[6]); // count
+    }
+
+    private void checkWakeupReason(String[] parts) {
+        assertTrue(parts.length >= 7);
+        for (int i = 4; i < parts.length-2; i++) {
+            assertNotNull(parts[i]); // part of wakeup
+        }
+        assertInteger(parts[parts.length-2]); // totalTime
+        assertInteger(parts[parts.length-1]); // count
+    }
+
+    private void checkNetwork(String[] parts) {
+        assertEquals(14, parts.length);
+        assertInteger(parts[4]);  // mobileBytesRx
+        assertInteger(parts[5]);  // mobileBytesTx
+        assertInteger(parts[6]);  // wifiBytesRx
+        assertInteger(parts[7]);  // wifiBytesTx
+        assertInteger(parts[8]);  // mobilePacketsRx
+        assertInteger(parts[9]);  // mobilePacketsTx
+        assertInteger(parts[10]); // wifiPacketsRx
+        assertInteger(parts[11]); // wifiPacketsTx
+        assertInteger(parts[12]); // mobileActiveTime (usec)
+        assertInteger(parts[13]); // mobileActiveCount
+    }
+
+    private void checkUserActivity(String[] parts) {
+        assertEquals(7, parts.length);
+        assertInteger(parts[4]); // other
+        assertInteger(parts[5]); // button
+        assertInteger(parts[6]); // touch
+    }
+
+    private void checkBattery(String[] parts) {
+        assertEquals(12, parts.length);
+        if (!parts[4].equals("N/A")) {
+            assertInteger(parts[4]);  // startCount
+        }
+        assertInteger(parts[5]);  // batteryRealtime
+        assertInteger(parts[6]);  // batteryUptime
+        assertInteger(parts[7]);  // totalRealtime
+        assertInteger(parts[8]);  // totalUptime
+        assertInteger(parts[9]);  // startClockTime
+        assertInteger(parts[10]); // batteryScreenOffRealtime
+        assertInteger(parts[11]); // batteryScreenOffUptime
+    }
+
+    private void checkBatteryDischarge(String[] parts) {
+        assertEquals(8, parts.length);
+        assertInteger(parts[4]); // low
+        assertInteger(parts[5]); // high
+        assertInteger(parts[6]); // screenOn
+        assertInteger(parts[7]); // screenOff
+    }
+
+    private void checkBatteryLevel(String[] parts) {
+        assertEquals(6, parts.length);
+        assertInteger(parts[4]); // startLevel
+        assertInteger(parts[5]); // currentLevel
+    }
+
+    private void checkWifi(String[] parts) {
+        assertEquals(7, parts.length);
+        assertInteger(parts[4]); // fullWifiLockOnTime (usec)
+        assertInteger(parts[5]); // wifiScanTime (usec)
+        assertInteger(parts[6]); // uidWifiRunningTime (usec)
+    }
+
+    private void checkMisc(String[] parts) {
+        assertEquals(20, parts.length);
+        assertInteger(parts[4]);      // screenOnTime
+        assertInteger(parts[5]);      // phoneOnTime
+        assertInteger(parts[6]);      // wifiOnTime
+        assertInteger(parts[7]);      // wifiRunningTime
+        assertInteger(parts[8]);      // bluetoothOnTime
+        assertInteger(parts[9]);      // mobileRxTotalBytes
+        assertInteger(parts[10]);     // mobileTxTotalBytes
+        assertInteger(parts[11]);     // wifiRxTotalBytes
+        assertInteger(parts[12]);     // wifiTxTotalBytes
+        assertInteger(parts[13]);     // fullWakeLockTimeTotal
+        assertInteger(parts[14]);     // partialWakeLockTimeTotal
+        assertEquals("0", parts[15]); // legacy input event count
+        assertInteger(parts[16]);     // mobileRadioActiveTime
+        assertInteger(parts[17]);     // mobileRadioActiveAdjustedTime
+        assertInteger(parts[18]);     // interactiveTime
+        assertInteger(parts[19]);     // lowPowerModeEnabledTime
+    }
+
+    private void checkGlobalNetwork(String[] parts) {
+        assertEquals(12, parts.length);
+        assertInteger(parts[4]);  // mobileRxTotalBytes
+        assertInteger(parts[5]);  // mobileTxTotalBytes
+        assertInteger(parts[6]);  // wifiRxTotalBytes
+        assertInteger(parts[7]);  // wifiTxTotalBytes
+        assertInteger(parts[8]);  // mobileRxTotalPackets
+        assertInteger(parts[9]);  // mobileTxTotalPackets
+        assertInteger(parts[10]); // wifiRxTotalPackets
+        assertInteger(parts[11]); // wifiTxTotalPackets
+    }
+
+    private void checkScreenBrightness(String[] parts) {
+        assertEquals(9, parts.length);
+        assertInteger(parts[4]); // dark
+        assertInteger(parts[5]); // dim
+        assertInteger(parts[6]); // medium
+        assertInteger(parts[7]); // light
+        assertInteger(parts[8]); // bright
+    }
+
+    private void checkSignalStrength(String[] parts) {
+        assertEquals(9, parts.length);
+        assertInteger(parts[4]); // none
+        assertInteger(parts[5]); // poor
+        assertInteger(parts[6]); // moderate
+        assertInteger(parts[7]); // good
+        assertInteger(parts[8]); // great
+    }
+
+    private void checkSignalScanningTime(String[] parts) {
+        assertEquals(5, parts.length);
+        assertInteger(parts[4]); // signalScanningTime
+    }
+
+    private void checkDataConnection(String[] parts) {
+        assertEquals(21, parts.length);
+        assertInteger(parts[4]);  // none
+        assertInteger(parts[5]);  // gprs
+        assertInteger(parts[6]);  // edge
+        assertInteger(parts[7]);  // umts
+        assertInteger(parts[8]);  // cdma
+        assertInteger(parts[9]);  // evdo_0
+        assertInteger(parts[10]); // evdo_A
+        assertInteger(parts[11]); // 1xrtt
+        assertInteger(parts[12]); // hsdpa
+        assertInteger(parts[13]); // hsupa
+        assertInteger(parts[14]); // hspa
+        assertInteger(parts[15]); // iden
+        assertInteger(parts[16]); // evdo_b
+        assertInteger(parts[17]); // lte
+        assertInteger(parts[18]); // ehrpd
+        assertInteger(parts[19]); // hspap
+        assertInteger(parts[20]); // other
+    }
+
+    private void checkWifiState(String[] parts) {
+        assertEquals(12, parts.length);
+        assertInteger(parts[4]);  // off
+        assertInteger(parts[5]);  // scanning
+        assertInteger(parts[6]);  // no_net
+        assertInteger(parts[7]);  // disconn
+        assertInteger(parts[8]);  // sta
+        assertInteger(parts[9]);  // p2p
+        assertInteger(parts[10]); // sta_p2p
+        assertInteger(parts[11]); // soft_ap
+    }
+
+    private void checkWifiSupplState(String[] parts) {
+        assertEquals(17, parts.length);
+        assertInteger(parts[4]);  // inv
+        assertInteger(parts[5]);  // dsc
+        assertInteger(parts[6]);  // dis
+        assertInteger(parts[7]);  // inact
+        assertInteger(parts[8]);  // scan
+        assertInteger(parts[9]);  // auth
+        assertInteger(parts[10]); // ascing
+        assertInteger(parts[11]); // asced
+        assertInteger(parts[12]); // 4-way
+        assertInteger(parts[13]); // group
+        assertInteger(parts[14]); // compl
+        assertInteger(parts[15]); // dorm
+        assertInteger(parts[16]); // uninit
+    }
+
+    private void checkWifiSignalStrength(String[] parts) {
+        assertEquals(9, parts.length);
+        assertInteger(parts[4]); // none
+        assertInteger(parts[5]); // poor
+        assertInteger(parts[6]); // moderate
+        assertInteger(parts[7]); // good
+        assertInteger(parts[8]); // great
+    }
+
+    private void checkBluetoothState(String[] parts) {
+        assertEquals(8, parts.length);
+        assertInteger(parts[4]); // inactive
+        assertInteger(parts[5]); // low
+        assertInteger(parts[6]); // med
+        assertInteger(parts[7]); // high
+    }
+
+    private void checkPowerUseSummary(String[] parts) {
+        assertEquals(8, parts.length);
+        assertDouble(parts[4]); // batteryCapacity
+        assertDouble(parts[5]); // computedPower
+        assertDouble(parts[6]); // minDrainedPower
+        assertDouble(parts[7]); // maxDrainedPower
+    }
+
+    private void checkPowerUseItem(String[] parts) {
+        assertEquals(6, parts.length);
+        assertNotNull(parts[4]); // label
+        assertDouble(parts[5]);  // mAh
+    }
+
+    private void checkChargeDischargeStep(String[] parts) {
+        assertEquals(8, parts.length);
+        assertInteger(parts[4]); // duration
+        if (!parts[5].equals("?")) {
+            assertInteger(parts[5]); // level
+        }
+        assertNotNull(parts[6]); // screen
+        assertNotNull(parts[7]); // power-save
+    }
+
+    private void checkDischargeTimeRemain(String[] parts) {
+        assertEquals(5, parts.length);
+        assertInteger(parts[4]); // batteryTimeRemaining
+    }
+
+    private void checkChargeTimeRemain(String[] parts) {
+        assertEquals(5, parts.length);
+        assertInteger(parts[4]); // chargeTimeRemaining
+    }
+
+    private static void assertInteger(String input) {
+        try {
+            Long.parseLong(input);
+        } catch (NumberFormatException e) {
+            fail("Expected an integer but found \"" + input + "\"");
+        }
+    }
+
+    private static void assertDouble(String input) {
+        try {
+            Double.parseDouble(input);
+        } catch (NumberFormatException e) {
+            fail("Expected a double but found \"" + input + "\"");
+        }
+    }
+
+    private static void assertSeenTag(Set<String> seenTags, String tag) {
+        assertTrue("No line starting with \"" + tag + ",\"", seenTags.contains(tag));
+    }
+}
diff --git a/hostsidetests/monkey/src/com/android/cts/monkey/MonkeyTest.java b/hostsidetests/monkey/src/com/android/cts/monkey/MonkeyTest.java
index f141d8f..997f7c6 100644
--- a/hostsidetests/monkey/src/com/android/cts/monkey/MonkeyTest.java
+++ b/hostsidetests/monkey/src/com/android/cts/monkey/MonkeyTest.java
@@ -37,7 +37,8 @@
     }
 
     private void assertIsUserAMonkey(boolean isMonkey) throws DeviceNotAvailableException {
-        String logs = mDevice.executeAdbCommand("logcat", "-d", "MonkeyActivity:I", "*:S");
+        String logs = mDevice.executeAdbCommand(
+                "logcat", "-v", "brief", "-d", "MonkeyActivity:I", "*:S");
         boolean monkeyLogsFound = false;
         Scanner s = new Scanner(logs);
         try {
diff --git a/hostsidetests/monkey/test-apps/CtsMonkeyApp/AndroidManifest.xml b/hostsidetests/monkey/test-apps/CtsMonkeyApp/AndroidManifest.xml
index 849d6a4..a3dfc83 100644
--- a/hostsidetests/monkey/test-apps/CtsMonkeyApp/AndroidManifest.xml
+++ b/hostsidetests/monkey/test-apps/CtsMonkeyApp/AndroidManifest.xml
@@ -18,14 +18,14 @@
 
     <application android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
 
-        <activity android:name=".MonkeyActivity" android:screenOrientation="portrait">
+        <activity android:name=".MonkeyActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
         
-        <activity android:name=".BaboonActivity" android:screenOrientation="portrait">
+        <activity android:name=".BaboonActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.MONKEY" />
diff --git a/hostsidetests/sample/src/android/sample/cts/SampleHostTest.java b/hostsidetests/sample/src/android/sample/cts/SampleHostTest.java
index 3cc4aa9..ab7e0b0 100644
--- a/hostsidetests/sample/src/android/sample/cts/SampleHostTest.java
+++ b/hostsidetests/sample/src/android/sample/cts/SampleHostTest.java
@@ -123,7 +123,7 @@
         // Start the APK and wait for it to complete.
         mDevice.executeShellCommand(START_COMMAND);
         // Dump logcat.
-        String logs = mDevice.executeAdbCommand("logcat", "-d", CLASS + ":I", "*:S");
+        String logs = mDevice.executeAdbCommand("logcat", "-v", "brief", "-d", CLASS + ":I", "*:S");
         // Search for string.
         String testString = "";
         Scanner in = new Scanner(logs);
diff --git a/hostsidetests/security/Android.mk b/hostsidetests/security/Android.mk
index a42ee8a..0c976a3 100644
--- a/hostsidetests/security/Android.mk
+++ b/hostsidetests/security/Android.mk
@@ -23,12 +23,27 @@
 # Must match the package name in CtsTestCaseList.mk
 LOCAL_MODULE := CtsSecurityHostTestCases
 
+LOCAL_MODULE_CLASS := JAVA_LIBRARIES
+
 LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed-prebuilt
 
 LOCAL_CTS_TEST_PACKAGE := android.host.security
 
 LOCAL_JAVA_RESOURCE_FILES := $(HOST_OUT_EXECUTABLES)/sepolicy-analyze
 
+selinux_general_policy := $(call intermediates-dir-for,ETC,general_sepolicy.conf)/general_sepolicy.conf
+
+selinux_neverallow_gen := cts/tools/selinux/SELinuxNeverallowTestGen.py
+
+selinux_neverallow_gen_data := cts/tools/selinux/SELinuxNeverallowTestFrame.py
+
+LOCAL_GENERATED_SOURCES := $(call local-generated-sources-dir)/android/cts/security/SELinuxNeverallowRulesTest.java
+
+$(LOCAL_GENERATED_SOURCES) : PRIVATE_SELINUX_GENERAL_POLICY := $(selinux_general_policy)
+$(LOCAL_GENERATED_SOURCES) : $(selinux_neverallow_gen) $(selinux_general_policy) $(selinux_neverallow_gen_data)
+	mkdir -p $(dir $@)
+	$< $(PRIVATE_SELINUX_GENERAL_POLICY) $@
+
 include $(BUILD_CTS_HOST_JAVA_LIBRARY)
 
 include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java b/hostsidetests/security/src/android/cts/security/SELinuxHostTest.java
similarity index 76%
rename from hostsidetests/security/src/android/security/cts/SELinuxHostTest.java
rename to hostsidetests/security/src/android/cts/security/SELinuxHostTest.java
index e2c98fe..96845b1 100644
--- a/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java
+++ b/hostsidetests/security/src/android/cts/security/SELinuxHostTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.security.cts;
+package android.cts.security;
 
 import com.android.cts.tradefed.build.CtsBuildHelper;
 import com.android.ddmlib.Log;
@@ -26,9 +26,10 @@
 
 import java.io.BufferedReader;
 import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
-import java.io.FileOutputStream;
 import java.lang.String;
 import java.net.URL;
 import java.util.Scanner;
@@ -42,15 +43,42 @@
  */
 public class SELinuxHostTest extends DeviceTestCase {
 
+    private File sepolicyAnalyze;
+    private File devicePolicyFile;
+
     /**
      * A reference to the device under test.
      */
     private ITestDevice mDevice;
 
+    private File copyResourceToTempFile(String resName) throws IOException {
+        InputStream is = this.getClass().getResourceAsStream(resName);
+        File tempFile = File.createTempFile("SELinuxHostTest", ".tmp");
+        FileOutputStream os = new FileOutputStream(tempFile);
+        int rByte = 0;
+        while ((rByte = is.read()) != -1) {
+            os.write(rByte);
+        }
+        os.flush();
+        os.close();
+        tempFile.deleteOnExit();
+        return tempFile;
+    }
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();
         mDevice = getDevice();
+
+        /* retrieve the sepolicy-analyze executable from jar */
+        sepolicyAnalyze = copyResourceToTempFile("/sepolicy-analyze");
+        sepolicyAnalyze.setExecutable(true);
+
+        /* obtain sepolicy file from running device */
+        devicePolicyFile = File.createTempFile("sepolicy", ".tmp");
+        devicePolicyFile.deleteOnExit();
+        mDevice.executeAdbCommand("pull", "/sys/fs/selinux/policy",
+                devicePolicyFile.getAbsolutePath());
     }
 
     /**
@@ -60,25 +88,9 @@
      */
     public void testAllEnforcing() throws Exception {
 
-        /* retrieve the sepolicy-analyze executable from jar */
-        InputStream is = this.getClass().getResourceAsStream("/sepolicy-analyze");
-        File execFile = File.createTempFile("sepolicy-analyze", ".tmp");
-        FileOutputStream os = new FileOutputStream(execFile);
-        int rByte = 0;
-        while ((rByte = is.read()) != -1) {
-            os.write(rByte);
-        }
-        os.flush();
-        os.close();
-        execFile.setExecutable(true);
-
-        /* obtain sepolicy file from running device */
-        File policyFile = File.createTempFile("sepolicy", ".tmp");
-        mDevice.executeAdbCommand("pull", "/sys/fs/selinux/policy", policyFile.getAbsolutePath());
-
         /* run sepolicy-analyze permissive check on policy file */
-        ProcessBuilder pb = new ProcessBuilder(execFile.getAbsolutePath(), "-p", "-P",
-                policyFile.getAbsolutePath());
+        ProcessBuilder pb = new ProcessBuilder(sepolicyAnalyze.getAbsolutePath(),
+                devicePolicyFile.getAbsolutePath(), "permissive");
         pb.redirectOutput(ProcessBuilder.Redirect.PIPE);
         pb.redirectErrorStream(true);
         Process p = pb.start();
@@ -90,10 +102,6 @@
             errorString.append(line);
             errorString.append("\n");
         }
-
-        /* clean up and check condition */
-        execFile.delete();
-        policyFile.delete();
         assertTrue("The following SELinux domains were found to be in permissive mode:\n"
                    + errorString, errorString.length() == 0);
     }
diff --git a/hostsidetests/theme/assets/21/hdpi.zip b/hostsidetests/theme/assets/21/hdpi.zip
index 9cc179c..0fa67b7 100644
--- a/hostsidetests/theme/assets/21/hdpi.zip
+++ b/hostsidetests/theme/assets/21/hdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/21/ldpi.zip b/hostsidetests/theme/assets/21/ldpi.zip
index 059bf33..e33f2f0 100644
--- a/hostsidetests/theme/assets/21/ldpi.zip
+++ b/hostsidetests/theme/assets/21/ldpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/21/mdpi.zip b/hostsidetests/theme/assets/21/mdpi.zip
index 0fb0778..f74739e 100644
--- a/hostsidetests/theme/assets/21/mdpi.zip
+++ b/hostsidetests/theme/assets/21/mdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/src/android/theme/cts/ComparisonTask.java b/hostsidetests/theme/src/android/theme/cts/ComparisonTask.java
index 33e67e5..ba880d7 100644
--- a/hostsidetests/theme/src/android/theme/cts/ComparisonTask.java
+++ b/hostsidetests/theme/src/android/theme/cts/ComparisonTask.java
@@ -37,9 +37,7 @@
 
     private static final int IMAGE_THRESHOLD = 2;
 
-    private static final String STORAGE_PATH_DEVICE = "/storage/emulated/legacy/cts-holo-assets/%s.png";
-
-    private static final String STORAGE_PATH_EMULATOR = "/sdcard/cts-holo-assets/%s.png";
+    private static final String STORAGE_PATH_DEVICE = "/sdcard/cts-holo-assets/%s.png";
 
     private final ITestDevice mDevice;
 
@@ -47,18 +45,10 @@
 
     private final String mName;
 
-    private final String mStoragePath;
-
     public ComparisonTask(ITestDevice device, File reference, String name) {
         mDevice = device;
         mReference = reference;
         mName = name;
-
-        if (mDevice.getSerialNumber().startsWith("emulator-")) {
-            mStoragePath = STORAGE_PATH_EMULATOR;
-        } else {
-            mStoragePath = STORAGE_PATH_DEVICE;
-        }
     }
 
     public Boolean call() {
@@ -67,7 +57,7 @@
         try {
             generated = File.createTempFile("gen_" + mName, ".png");
 
-            final String remoteGenerated = String.format(mStoragePath, mName);
+            final String remoteGenerated = String.format(STORAGE_PATH_DEVICE, mName);
             if (!mDevice.doesFileExist(remoteGenerated)) {
                 Log.logAndDisplay(LogLevel.ERROR, TAG, "File " + remoteGenerated + " have not been saved on device");
                 return false;
@@ -84,7 +74,7 @@
                 Log.logAndDisplay(LogLevel.INFO, TAG, "Diff created: " + diff.getPath());
             }
         } catch (Exception e) {
-            Log.logAndDisplay(LogLevel.ERROR, TAG, String.format(mStoragePath, mName));
+            Log.logAndDisplay(LogLevel.ERROR, TAG, String.format(STORAGE_PATH_DEVICE, mName));
             Log.logAndDisplay(LogLevel.ERROR, TAG, e.toString());
             e.printStackTrace();
         } finally {
diff --git a/hostsidetests/theme/src/android/theme/cts/ThemeHostTest.java b/hostsidetests/theme/src/android/theme/cts/ThemeHostTest.java
index 90a0c72..da94b15 100644
--- a/hostsidetests/theme/src/android/theme/cts/ThemeHostTest.java
+++ b/hostsidetests/theme/src/android/theme/cts/ThemeHostTest.java
@@ -322,7 +322,8 @@
         boolean waiting = true;
         while (waiting) {
             // Dump logcat.
-            final String logs = mDevice.executeAdbCommand("logcat", "-d", CLASS + ":I", "*:S");
+            final String logs = mDevice.executeAdbCommand(
+                    "logcat", "-v", "brief", "-d", CLASS + ":I", "*:S");
             // Search for string.
             final Scanner in = new Scanner(logs);
             while (in.hasNextLine()) {
diff --git a/hostsidetests/usb/src/com/android/cts/usb/TestUsbTest.java b/hostsidetests/usb/src/com/android/cts/usb/TestUsbTest.java
index 4736e51..3af52c0 100644
--- a/hostsidetests/usb/src/com/android/cts/usb/TestUsbTest.java
+++ b/hostsidetests/usb/src/com/android/cts/usb/TestUsbTest.java
@@ -40,13 +40,11 @@
  */
 public class TestUsbTest extends DeviceTestCase implements IAbiReceiver, IBuildReceiver {
 
-    private static final String LOG_TAG = "TestUsbTest";
     private static final String CTS_RUNNER = "android.support.test.runner.AndroidJUnitRunner";
     private static final String PACKAGE_NAME = "com.android.cts.usb.serialtest";
     private static final String APK_NAME="CtsUsbSerialTestApp.apk";
     private ITestDevice mDevice;
     private IAbi mAbi;
-    private String mAbiBitness;
     private CtsBuildHelper mBuild;
 
     @Override
@@ -118,7 +116,8 @@
         if (runResult.isRunFailure()) {
             fail(runResult.getRunFailureMessage());
         }
-        String logs = mDevice.executeAdbCommand("logcat", "-d", "CtsUsbSerialTest:W", "*:S");
+        String logs = mDevice.executeAdbCommand(
+                "logcat", "-v", "brief", "-d", "CtsUsbSerialTest:W", "*:S");
         pattern = Pattern.compile("^.*CtsUsbSerialTest\\(.*\\):\\s+([a-zA-Z0-9]{6,20})",
                 Pattern.MULTILINE);
         matcher = pattern.matcher(logs);
diff --git a/libs/deviceutil/src/android/cts/util/MediaUtils.java b/libs/deviceutil/src/android/cts/util/MediaUtils.java
new file mode 100644
index 0000000..eab4808
--- /dev/null
+++ b/libs/deviceutil/src/android/cts/util/MediaUtils.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright 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.cts.util;
+
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.net.Uri;
+import java.lang.reflect.Method;
+import static java.lang.reflect.Modifier.isPublic;
+import static java.lang.reflect.Modifier.isStatic;
+import java.util.Map;
+import android.util.Log;
+
+import java.io.IOException;
+
+public class MediaUtils {
+    private static final String TAG = "MediaUtils";
+
+    private static final int ALL_AV_TRACKS = -1;
+
+    private static final MediaCodecList sMCL = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+
+
+    /**
+     * Finds test name (heuristically) and prints out standard skip message.
+     *
+     * Since it uses heuristics, this method has only been verified for media
+     * tests. This centralizes the way to signal a skipped test.
+     */
+    public static void skipTest(String tag, String reason) {
+        int bestScore = -1;
+        String testName = "test???";
+        Map<Thread, StackTraceElement[]> traces = Thread.getAllStackTraces();
+        for (Map.Entry<Thread, StackTraceElement[]> entry : traces.entrySet()) {
+            StackTraceElement[] stack = entry.getValue();
+            for (int index = 0; index < stack.length; ++index) {
+                // method name must start with "test"
+                String methodName = stack[index].getMethodName();
+                if (!methodName.startsWith("test")) {
+                    continue;
+                }
+
+                int score = 0;
+                // see if there is a public non-static void method that takes no argument
+                Class<?> clazz;
+                try {
+                    clazz = Class.forName(stack[index].getClassName());
+                    ++score;
+                    for (final Method method : clazz.getDeclaredMethods()) {
+                        if (method.getName().equals(methodName)
+                                && isPublic(method.getModifiers())
+                                && !isStatic(method.getModifiers())
+                                && method.getParameterTypes().length == 0
+                                && method.getReturnType().equals(Void.TYPE)) {
+                            ++score;
+                            break;
+                        }
+                    }
+                    if (score == 1) {
+                        // if we could read the class, but method is not public void, it is
+                        // not a candidate
+                        continue;
+                    }
+                } catch (ClassNotFoundException e) {
+                }
+
+                // even if we cannot verify the method signature, there are signals in the stack
+
+                // usually test method is invoked by reflection
+                int depth = 1;
+                while (index + depth < stack.length
+                        && stack[index + depth].getMethodName().equals("invoke")
+                        && stack[index + depth].getClassName().equals(
+                                "java.lang.reflect.Method")) {
+                    ++depth;
+                }
+                if (depth > 1) {
+                    ++score;
+                    // and usually test method is run by runMethod method in android.test package
+                    if (index + depth < stack.length) {
+                        if (stack[index + depth].getClassName().startsWith("android.test.")) {
+                            ++score;
+                        }
+                        if (stack[index + depth].getMethodName().equals("runMethod")) {
+                            ++score;
+                        }
+                    }
+                }
+
+                if (score > bestScore) {
+                    bestScore = score;
+                    testName = methodName;
+                }
+            }
+        }
+
+        Log.i(tag, "SKIPPING " + testName + "(): " + reason);
+    }
+
+    /**
+     * Finds test name (heuristically) and prints out standard skip message.
+     *
+     * Since it uses heuristics, this method has only been verified for media
+     * tests.  This centralizes the way to signal a skipped test.
+     */
+    public static void skipTest(String reason) {
+        skipTest(TAG, reason);
+    }
+
+    public static boolean check(boolean result, String message) {
+        if (!result) {
+            skipTest(message);
+        }
+        return result;
+    }
+
+    public static boolean canDecode(MediaFormat format) {
+        if (sMCL.findDecoderForFormat(format) == null) {
+            Log.i(TAG, "no decoder for " + format);
+            return false;
+        }
+        return true;
+    }
+
+    public static boolean hasCodecForTrack(MediaExtractor ex, int track) {
+        int count = ex.getTrackCount();
+        if (track < 0 || track >= count) {
+            throw new IndexOutOfBoundsException(track + " not in [0.." + (count - 1) + "]");
+        }
+        return canDecode(ex.getTrackFormat(track));
+    }
+
+    /**
+     * return true iff all audio and video tracks are supported
+     */
+    public static boolean hasCodecsForMedia(MediaExtractor ex) {
+        for (int i = 0; i < ex.getTrackCount(); ++i) {
+            MediaFormat format = ex.getTrackFormat(i);
+            // only check for audio and video codecs
+            String mime = format.getString(MediaFormat.KEY_MIME).toLowerCase();
+            if (!mime.startsWith("audio/") && !mime.startsWith("video/")) {
+                continue;
+            }
+            if (!canDecode(format)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * return true iff any track starting with mimePrefix is supported
+     */
+    public static boolean hasCodecForMediaAndDomain(MediaExtractor ex, String mimePrefix) {
+        mimePrefix = mimePrefix.toLowerCase();
+        for (int i = 0; i < ex.getTrackCount(); ++i) {
+            MediaFormat format = ex.getTrackFormat(i);
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            if (mime.toLowerCase().startsWith(mimePrefix)) {
+                if (canDecode(format)) {
+                    return true;
+                }
+                Log.i(TAG, "no decoder for " + format);
+            }
+        }
+        return false;
+    }
+
+    private static boolean hasCodecsForResourceCombo(
+            Context context, int resourceId, int track, String mimePrefix) {
+        try {
+            AssetFileDescriptor afd = null;
+            MediaExtractor ex = null;
+            try {
+                afd = context.getResources().openRawResourceFd(resourceId);
+                ex = new MediaExtractor();
+                ex.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
+                if (mimePrefix != null) {
+                    return hasCodecForMediaAndDomain(ex, mimePrefix);
+                } else if (track == ALL_AV_TRACKS) {
+                    return hasCodecsForMedia(ex);
+                } else {
+                    return hasCodecForTrack(ex, track);
+                }
+            } finally {
+                if (ex != null) {
+                    ex.release();
+                }
+                if (afd != null) {
+                    afd.close();
+                }
+            }
+        } catch (IOException e) {
+            Log.i(TAG, "could not open resource");
+        }
+        return false;
+    }
+
+    /**
+     * return true iff all audio and video tracks are supported
+     */
+    public static boolean hasCodecsForResource(Context context, int resourceId) {
+        return hasCodecsForResourceCombo(context, resourceId, ALL_AV_TRACKS, null /* mimePrefix */);
+    }
+
+    public static boolean checkCodecsForResource(Context context, int resourceId) {
+        return check(hasCodecsForResource(context, resourceId), "no decoder found");
+    }
+
+    /**
+     * return true iff track is supported.
+     */
+    public static boolean hasCodecForResource(Context context, int resourceId, int track) {
+        return hasCodecsForResourceCombo(context, resourceId, track, null /* mimePrefix */);
+    }
+
+    public static boolean checkCodecForResource(Context context, int resourceId, int track) {
+        return check(hasCodecForResource(context, resourceId, track), "no decoder found");
+    }
+
+    /**
+     * return true iff any track starting with mimePrefix is supported
+     */
+    public static boolean hasCodecForResourceAndDomain(
+            Context context, int resourceId, String mimePrefix) {
+        return hasCodecsForResourceCombo(context, resourceId, ALL_AV_TRACKS, mimePrefix);
+    }
+
+    /**
+     * return true iff all audio and video tracks are supported
+     */
+    public static boolean hasCodecsForPath(Context context, String path) {
+        MediaExtractor ex = null;
+        try {
+            ex = new MediaExtractor();
+            Uri uri = Uri.parse(path);
+            String scheme = uri.getScheme();
+            if (scheme == null) { // file
+                ex.setDataSource(path);
+            } else if (scheme.equalsIgnoreCase("file")) {
+                ex.setDataSource(uri.getPath());
+            } else {
+                ex.setDataSource(context, uri, null);
+            }
+            return hasCodecsForMedia(ex);
+        } catch (IOException e) {
+            Log.i(TAG, "could not open path " + path);
+        } finally {
+            if (ex != null) {
+                ex.release();
+            }
+        }
+        return false;
+    }
+
+    public static boolean checkCodecsForPath(Context context, String path) {
+        return check(hasCodecsForPath(context, path), "no decoder found");
+    }
+
+    private static boolean hasCodecForMime(boolean encoder, String mime) {
+        for (MediaCodecInfo info : sMCL.getCodecInfos()) {
+            if (encoder != info.isEncoder()) {
+                continue;
+            }
+
+            for (String type : info.getSupportedTypes()) {
+                if (type.equalsIgnoreCase(mime)) {
+                    Log.i(TAG, "found codec " + info.getName() + " for mime " + mime);
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private static boolean hasCodecForMimes(boolean encoder, String[] mimes) {
+        for (String mime : mimes) {
+            if (!hasCodecForMime(encoder, mime)) {
+                Log.i(TAG, "no " + (encoder ? "encoder" : "decoder") + " for mime " + mime);
+                return false;
+            }
+        }
+        return true;
+    }
+
+
+    public static boolean hasEncoder(String... mimes) {
+        return hasCodecForMimes(true /* encoder */, mimes);
+    }
+
+    public static boolean hasDecoder(String... mimes) {
+        return hasCodecForMimes(false /* encoder */, mimes);
+    }
+
+    public static boolean checkDecoder(String... mimes) {
+        return check(hasCodecForMimes(false /* encoder */, mimes), "no decoder found");
+    }
+
+    public static boolean checkEncoder(String... mimes) {
+        return check(hasCodecForMimes(true /* encoder */, mimes), "no encoder found");
+    }
+
+    public static boolean canDecodeVideo(String mime, int width, int height, float rate) {
+        MediaFormat format = MediaFormat.createVideoFormat(mime, width, height);
+        format.setFloat(MediaFormat.KEY_FRAME_RATE, rate);
+        return canDecode(format);
+    }
+
+    public static boolean checkDecoderForFormat(MediaFormat format) {
+        return check(canDecode(format), "no decoder for " + format);
+    }
+}
diff --git a/suite/cts/deviceTests/videoperf/Android.mk b/suite/cts/deviceTests/videoperf/Android.mk
index cb398a9..cd82dde 100644
--- a/suite/cts/deviceTests/videoperf/Android.mk
+++ b/suite/cts/deviceTests/videoperf/Android.mk
@@ -24,7 +24,7 @@
 
 LOCAL_PACKAGE_NAME := CtsDeviceVideoPerf
 
-LOCAL_SDK_VERSION := 16
+LOCAL_SDK_VERSION := current
 
 include $(BUILD_CTS_PACKAGE)
 
diff --git a/suite/cts/deviceTests/videoperf/src/com/android/cts/videoperf/CodecInfo.java b/suite/cts/deviceTests/videoperf/src/com/android/cts/videoperf/CodecInfo.java
index b9edfa4..b7d1d27 100644
--- a/suite/cts/deviceTests/videoperf/src/com/android/cts/videoperf/CodecInfo.java
+++ b/suite/cts/deviceTests/videoperf/src/com/android/cts/videoperf/CodecInfo.java
@@ -19,7 +19,9 @@
 import android.media.MediaCodecInfo;
 import android.media.MediaCodecInfo.CodecCapabilities;
 import android.media.MediaCodecInfo.CodecProfileLevel;
+import android.media.MediaCodecInfo.VideoCapabilities;
 import android.media.MediaCodecList;
+import android.media.MediaFormat;
 import android.util.Log;
 
 
@@ -38,21 +40,33 @@
     public boolean mSupportPlanar = false;
 
     private static final String TAG = "CodecInfo";
-    private static final String VIDEO_AVC = "video/avc";
+    private static final String VIDEO_AVC = MediaFormat.MIMETYPE_VIDEO_AVC;
     /**
      * Check if given codec with given (w,h) is supported.
-     * @param mimeType codec type in mime format like "video/avc"
+     * @param mimeType codec type in mime format like MediaFormat.MIMETYPE_VIDEO_AVC
      * @param w video width
      * @param h video height
      * @param isEncoder whether the codec is encoder or decoder
      * @return null if the configuration is not supported.
      */
-    public static CodecInfo getSupportedFormatInfo(String mimeType, int w, int h,
-            boolean isEncoder) {
-        CodecCapabilities cap = getCodecCapability(mimeType, isEncoder);
-        if (cap == null) { // not supported
+    public static CodecInfo getSupportedFormatInfo(
+            String mimeType, int w, int h, boolean isEncoder) {
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        MediaFormat format = MediaFormat.createVideoFormat(mimeType, w, h);
+        String codec = isEncoder
+                ? mcl.findEncoderForFormat(format)
+                : mcl.findDecoderForFormat(format);
+        if (codec == null) { // not supported
             return null;
         }
+        CodecCapabilities cap = null;
+        for (MediaCodecInfo info : mcl.getCodecInfos()) {
+            if (info.getName().equals(codec)) {
+                cap = info.getCapabilitiesForType(mimeType);
+                break;
+            }
+        }
+        VideoCapabilities vidCap = cap.getVideoCapabilities();
         CodecInfo info = new CodecInfo();
         for (int color : cap.colorFormats) {
             if (color == CodecCapabilities.COLOR_FormatYUV420SemiPlanar) {
@@ -70,129 +84,13 @@
         }
 
         if (mimeType.equals(VIDEO_AVC)) {
-            int highestLevel = 0;
-            for (CodecProfileLevel lvl : cap.profileLevels) {
-                if (lvl.level > highestLevel) {
-                    highestLevel = lvl.level;
-                }
-            }
-            Log.i(TAG, "Avc highest level " + Integer.toHexString(highestLevel));
-            int maxW = 0;
-            int maxH = 0;
-            int bitRate = 0;
-            int mbW = (w + 15) / 16; // size in macroblocks
-            int mbH = (h + 15) / 16;
-            int maxMacroblocksPerSecond = 0; // max decoding speed
-            switch(highestLevel) {
-            // Do not support Level 1 to 2.
-            case CodecProfileLevel.AVCLevel1:
-            case CodecProfileLevel.AVCLevel11:
-            case CodecProfileLevel.AVCLevel12:
-            case CodecProfileLevel.AVCLevel13:
-            case CodecProfileLevel.AVCLevel1b:
-            case CodecProfileLevel.AVCLevel2:
-                return null;
-            case CodecProfileLevel.AVCLevel21:
-                maxW = 352;
-                maxH = 576;
-                bitRate = 4000000;
-                maxMacroblocksPerSecond = 19800;
-                break;
-            case CodecProfileLevel.AVCLevel22:
-                maxW = 720;
-                maxH = 480;
-                bitRate = 4000000;
-                maxMacroblocksPerSecond = 20250;
-                break;
-            case CodecProfileLevel.AVCLevel3:
-                maxW = 720;
-                maxH = 480;
-                bitRate = 10000000;
-                maxMacroblocksPerSecond = 40500;
-                break;
-            case CodecProfileLevel.AVCLevel31:
-                maxW = 1280;
-                maxH = 720;
-                bitRate = 14000000;
-                maxMacroblocksPerSecond = 108000;
-                break;
-            case CodecProfileLevel.AVCLevel32:
-                maxW = 1280;
-                maxH = 720;
-                bitRate = 20000000;
-                maxMacroblocksPerSecond = 216000;
-                break;
-            case CodecProfileLevel.AVCLevel4:
-                maxW = 1920;
-                maxH = 1080;
-                bitRate = 20000000;
-                maxMacroblocksPerSecond = 245760;
-                break;
-            case CodecProfileLevel.AVCLevel41:
-                maxW = 1920;
-                maxH = 1080;
-                bitRate = 50000000;
-                maxMacroblocksPerSecond = 245760;
-                break;
-            case CodecProfileLevel.AVCLevel42:
-                maxW = 2048;
-                maxH = 1080;
-                bitRate = 50000000;
-                maxMacroblocksPerSecond = 522240;
-                break;
-            case CodecProfileLevel.AVCLevel5:
-                maxW = 3672;
-                maxH = 1536;
-                bitRate = 135000000;
-                maxMacroblocksPerSecond = 589824;
-                break;
-            case CodecProfileLevel.AVCLevel51:
-            default:
-                maxW = 4096;
-                maxH = 2304;
-                bitRate = 240000000;
-                maxMacroblocksPerSecond = 983040;
-                break;
-            }
-            if ((w > maxW) || (h > maxH)) {
-                Log.i(TAG, "Requested resolution (" + w + "," + h + ") exceeds (" +
-                        maxW + "," + maxH + ")");
-                return null;
-            }
-            info.mFps = maxMacroblocksPerSecond / mbH / mbW;
-            info.mBitRate = bitRate;
-            Log.i(TAG, "AVC Level " + Integer.toHexString(highestLevel) + " bit rate " + bitRate +
-                    " fps " + info.mFps);
+            info.mFps = vidCap.getSupportedFrameRatesFor(w, h).getUpper().intValue();
+            info.mBitRate = vidCap.getBitrateRange().getUpper();
+            Log.i(TAG, "AVC bit rate " + info.mBitRate + " fps " + info.mFps);
         }
         return info;
     }
 
-    /**
-     * Search for given codecName and returns CodecCapabilities if found
-     * @param codecName
-     * @param isEncoder true for encoder, false for decoder
-     * @return null if the codec is not supported
-     */
-    private static CodecCapabilities getCodecCapability(
-            String codecName, boolean isEncoder) {
-        int codecCount = MediaCodecList.getCodecCount();
-        for (int i = 0; i < codecCount; ++i) {
-            MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
-            String[] types = info.getSupportedTypes();
-            if (isEncoder != info.isEncoder()) {
-                continue;
-            }
-            for (int j = 0; j < types.length; ++j) {
-                if (types[j].compareTo(codecName) == 0) {
-                    CodecCapabilities cap = info.getCapabilitiesForType(types[j]);
-                    Log.i(TAG, "Use codec " + info.getName());
-                    return cap;
-                }
-            }
-        }
-        return null;
-    }
-
     // for debugging
     private static void printIntArray(String msg, int[] data) {
         StringBuilder builder = new StringBuilder();
diff --git a/suite/cts/deviceTests/videoperf/src/com/android/cts/videoperf/VideoEncoderDecoderTest.java b/suite/cts/deviceTests/videoperf/src/com/android/cts/videoperf/VideoEncoderDecoderTest.java
index a009ce2..aacb7a5 100644
--- a/suite/cts/deviceTests/videoperf/src/com/android/cts/videoperf/VideoEncoderDecoderTest.java
+++ b/suite/cts/deviceTests/videoperf/src/com/android/cts/videoperf/VideoEncoderDecoderTest.java
@@ -18,6 +18,7 @@
 
 import android.graphics.Point;
 import android.media.MediaCodec;
+import android.media.MediaCodecList;
 import android.media.MediaCodecInfo.CodecCapabilities;
 import android.media.MediaFormat;
 import android.util.Log;
@@ -27,6 +28,7 @@
 import com.android.cts.util.ResultUnit;
 import com.android.cts.util.Stat;
 
+import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.lang.System;
 import java.util.Random;
@@ -49,7 +51,7 @@
     // is not very high.
     private static final long VIDEO_CODEC_WAIT_TIME_US = 5000;
     private static final boolean VERBOSE = false;
-    private static final String VIDEO_AVC = "video/avc";
+    private static final String VIDEO_AVC = MediaFormat.MIMETYPE_VIDEO_AVC;
     private static final int TOTAL_FRAMES = 300;
     private static final int NUMBER_OF_REPEAT = 10;
     // i frame interval for encoder
@@ -130,12 +132,12 @@
      * @param numberRepeat how many times to repeat the encoding / decoding process
      */
     private void doTest(String mimeType, int w, int h, int numberRepeat) throws Exception {
-        CodecInfo infoEnc = CodecInfo.getSupportedFormatInfo(mimeType, w, h, true);
+        CodecInfo infoEnc = CodecInfo.getSupportedFormatInfo(mimeType, w, h, true /* encoder */);
         if (infoEnc == null) {
-            Log.i(TAG, "Codec " + mimeType + "with " + w + "," + h + " not supported");
+            Log.i(TAG, "Encoder " + mimeType + " with " + w + "," + h + " not supported");
             return;
         }
-        CodecInfo infoDec = CodecInfo.getSupportedFormatInfo(mimeType, w, h, false);
+        CodecInfo infoDec = CodecInfo.getSupportedFormatInfo(mimeType, w, h, false /* encoder */);
         assertNotNull(infoDec);
         mVideoWidth = w;
         mVideoHeight = h;
@@ -207,8 +209,11 @@
      * @return time taken in ms to encode the frames. This does not include initialization time.
      */
     private double runEncoder(String mimeType, MediaFormat format, int totalFrames) {
-        MediaCodec codec = MediaCodec.createEncoderByType(mimeType);
+        MediaCodec codec = null;
         try {
+            MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+            String encoderName = mcl.findEncoderForFormat(format);
+            codec = MediaCodec.createByCodecName(encoderName);
             codec.configure(
                     format,
                     null /* surface */,
@@ -216,7 +221,11 @@
                     MediaCodec.CONFIGURE_FLAG_ENCODE);
         } catch (IllegalStateException e) {
             Log.e(TAG, "codec '" + mimeType + "' failed configuration.");
+            codec.release();
             assertTrue("codec '" + mimeType + "' failed configuration.", false);
+        } catch (IOException | NullPointerException e) {
+            Log.i(TAG, "could not find codec for " + format);
+            return Double.NaN;
         }
         codec.start();
         ByteBuffer[] codecInputBuffers = codec.getInputBuffers();
@@ -347,7 +356,15 @@
      * @return returns length-2 array with 0: time for decoding, 1 : rms error of pixels
      */
     private double[] runDecoder(String mimeType, MediaFormat format) {
-        MediaCodec codec = MediaCodec.createDecoderByType(mimeType);
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        String decoderName = mcl.findDecoderForFormat(format);
+        MediaCodec codec = null;
+        try {
+            codec = MediaCodec.createByCodecName(decoderName);
+        } catch (IOException | NullPointerException e) {
+            Log.i(TAG, "could not find codec for " + format);
+            return null;
+        }
         codec.configure(format, null /* surface */, null /* crypto */, 0 /* flags */);
         codec.start();
         ByteBuffer[] codecInputBuffers = codec.getInputBuffers();
diff --git a/tests/JobScheduler/src/android/jobscheduler/MockJobService.java b/tests/JobScheduler/src/android/jobscheduler/MockJobService.java
index a0177e2..38a753d 100644
--- a/tests/JobScheduler/src/android/jobscheduler/MockJobService.java
+++ b/tests/JobScheduler/src/android/jobscheduler/MockJobService.java
@@ -34,7 +34,7 @@
     private static final String TAG = "MockJobService";
 
     /** Wait this long before timing out the test. */
-    private static final long DEFAULT_TIMEOUT_MILLIS = 5000L; // 5 seconds.
+    private static final long DEFAULT_TIMEOUT_MILLIS = 30000L; // 30 seconds.
 
     @Override
     public void onCreate() {
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/TimingConstraintsTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/TimingConstraintsTest.java
index 36f44ef..ed9cadd 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/TimingConstraintsTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/TimingConstraintsTest.java
@@ -29,7 +29,7 @@
 
     public void testScheduleOnce() throws Exception {
         JobInfo oneTimeJob = new JobInfo.Builder(TIMING_JOB_ID, kJobServiceComponent)
-                        .setOverrideDeadline(1000)  // 1 secs
+                        .setOverrideDeadline(5000)  // 5 secs
                         .build();
 
         kTestEnvironment.setExpectedExecutions(1);
@@ -41,7 +41,7 @@
     public void testSchedulePeriodic() throws Exception {
         JobInfo periodicJob =
                 new JobInfo.Builder(TIMING_JOB_ID, kJobServiceComponent)
-                        .setPeriodic(1000L)  // 1 second period.
+                        .setPeriodic(5000L)  // 5 second period.
                         .build();
 
         kTestEnvironment.setExpectedExecutions(3);
@@ -52,7 +52,8 @@
 
     public void testCancel() throws Exception {
         JobInfo cancelJob = new JobInfo.Builder(CANCEL_JOB_ID, kJobServiceComponent)
-                .setOverrideDeadline(2000L)
+                .setMinimumLatency(5000L) // make sure it doesn't actually run immediately
+                .setOverrideDeadline(7000L)
                 .build();
 
         kTestEnvironment.setExpectedExecutions(0);
diff --git a/tests/app/AndroidManifest.xml b/tests/app/AndroidManifest.xml
index b628a0c..0d61e20 100644
--- a/tests/app/AndroidManifest.xml
+++ b/tests/app/AndroidManifest.xml
@@ -40,6 +40,7 @@
     <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
     <uses-permission android:name="android.permission.SET_WALLPAPER_HINTS" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.BODY_SENSORS" />
 
     <application android:label="Android TestCase"
                 android:icon="@drawable/size_48x48"
diff --git a/tests/core/runner/src/com/android/cts/runner/CtsTestRunListener.java b/tests/core/runner/src/com/android/cts/runner/CtsTestRunListener.java
index 5196df1..080c08e 100644
--- a/tests/core/runner/src/com/android/cts/runner/CtsTestRunListener.java
+++ b/tests/core/runner/src/com/android/cts/runner/CtsTestRunListener.java
@@ -35,6 +35,7 @@
 import java.net.CookieHandler;
 import java.net.ResponseCache;
 import java.util.Locale;
+import java.util.Properties;
 import java.util.TimeZone;
 
 import javax.net.ssl.HostnameVerifier;
@@ -57,7 +58,7 @@
 
     @Override
     public void testRunStarted(Description description) throws Exception {
-        mEnvironment = new TestEnvironment();
+        mEnvironment = new TestEnvironment(getInstrumentation().getTargetContext());
 
         // We might want to move this to /sdcard, if is is mounted/writable.
         File cacheDir = getInstrumentation().getTargetContext().getCacheDir();
@@ -149,21 +150,29 @@
     static class TestEnvironment {
         private final Locale mDefaultLocale;
         private final TimeZone mDefaultTimeZone;
-        private final String mJavaIoTmpDir;
         private final HostnameVerifier mHostnameVerifier;
         private final SSLSocketFactory mSslSocketFactory;
+        private final Properties mProperties = new Properties();
 
-        TestEnvironment() {
+        TestEnvironment(Context context) {
             mDefaultLocale = Locale.getDefault();
             mDefaultTimeZone = TimeZone.getDefault();
-            mJavaIoTmpDir = System.getProperty("java.io.tmpdir");
             mHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
             mSslSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory();
+
+            mProperties.setProperty("user.home", "");
+            mProperties.setProperty("java.io.tmpdir", System.getProperty("java.io.tmpdir"));
+            // The CDD mandates that devices that support WiFi are the only ones that will have 
+            // multicast.
+            PackageManager pm = context.getPackageManager();
+            mProperties.setProperty("android.cts.device.multicast",
+                    Boolean.toString(pm.hasSystemFeature(PackageManager.FEATURE_WIFI)));
+
         }
 
         void reset() {
             System.setProperties(null);
-            System.setProperty("java.io.tmpdir", mJavaIoTmpDir);
+            System.setProperties(mProperties);
             Locale.setDefault(mDefaultLocale);
             TimeZone.setDefault(mDefaultTimeZone);
             Authenticator.setDefault(null);
diff --git a/tests/expectations/knownfailures.txt b/tests/expectations/knownfailures.txt
index dd8660f..3abb1f7 100644
--- a/tests/expectations/knownfailures.txt
+++ b/tests/expectations/knownfailures.txt
@@ -1,5 +1,12 @@
 [
 {
+  description: "testWindowContentFrameStats is flaky without 75b55d0846159543aafc1b7420915497fce9b3f1",
+  names: [
+    "android.app.uiautomation.cts.UiAutomationTest#testWindowContentFrameStats"
+  ],
+  bug: 18039218
+},
+{
   description: "the UsageStats is not yet stable enough",
   names: [
     "android.app.usage.cts.UsageStatsTest"
@@ -22,6 +29,30 @@
   bug: 17595050
 },
 {
+  description: "the SSLCertificateSocketFactoryTest often fails because of lack of live internet or short timeout, it should be refactored to do a local server testing",
+  names: [
+    "android.net.cts.SSLCertificateSocketFactoryTest#testCreateSocket",
+    "android.net.cts.SSLCertificateSocketFactoryTest#test_createSocket_bind",
+    "android.net.cts.SSLCertificateSocketFactoryTest#test_createSocket_simple",
+    "android.net.cts.SSLCertificateSocketFactoryTest#test_createSocket_wrapping"
+  ],
+  bug: 18682315
+},
+{
+  description: "the test result are too much dependent on live-internet connection, which for some devices might not exist",
+  names: [
+    "android.net.wifi.cts.NsdManagerTest#testAndroidTestCaseSetupProperly"
+  ],
+  bug: 18680089
+},
+{
+  description: "AudioPolicyBinder tests are not yet robust enough",
+  names: [
+    "android.security.cts.AudioPolicyBinderTest"
+  ],
+  bug: 18461670
+},
+{
   description: "Not all jdwp features are currently supported. These tests will fail",
   names: [
     "org.apache.harmony.jpda.tests.jdwp.DebuggerOnDemand.OnthrowDebuggerLaunchTest#testDebuggerLaunch001",
@@ -133,27 +164,22 @@
 {
   description: "New tests recently added for Android Enterprise. To be moved out of CTS-staging as soon as they show that they are stable",
   names: [
-    "com.android.cts.devicepolicy.DeviceOwnerTest#testApplicationRestrictions",
-    "com.android.cts.devicepolicy.DeviceOwnerTest#testCaCertManagement",
-    "com.android.cts.devicepolicy.DeviceOwnerTest#testDeviceOwnerSetup",
-    "com.android.cts.devicepolicy.DeviceOwnerTest#testPersistentIntentResolving",
-    "com.android.cts.devicepolicy.DeviceOwnerTest#testScreenCaptureDisabled",
-    "com.android.cts.devicepolicy.ManagedProfileTest#testManagedProfileSetup",
-    "com.android.cts.devicepolicy.ManagedProfileTest#testWipeData",
-    "com.android.cts.devicepolicy.ManagedProfileTest#testCrossProfileIntentFilters",
-    "com.android.cts.devicepolicy.ManagedProfileTest#testCrossProfileContent",
-    "com.android.cts.devicepolicy.ManagedProfileTest#testNoDebuggingFeaturesRestriction"
+    "com.android.cts.devicepolicy.LauncherAppsMultiUserTest#testGetActivitiesForNonProfileFails",
+    "com.android.cts.devicepolicy.LauncherAppsMultiUserTest#testNoLauncherCallbackPackageAddedSecondaryUser",
+    "com.android.cts.devicepolicy.LauncherAppsProfileTest#testGetActivitiesWithProfile",
+    "com.android.cts.devicepolicy.LauncherAppsProfileTest#testLauncherCallbackPackageAddedProfile",
+    "com.android.cts.devicepolicy.LauncherAppsProfileTest#testLauncherCallbackPackageRemovedProfile",
+    "com.android.cts.devicepolicy.LauncherAppsProfileTest#testLauncherCallbackPackageChangedProfile",
+    "com.android.cts.devicepolicy.LauncherAppsSingleUserTest#testInstallAppMainUser",
+    "com.android.cts.devicepolicy.LauncherAppsSingleUserTest#testLauncherCallbackPackageAddedMainUser",
+    "com.android.cts.devicepolicy.LauncherAppsSingleUserTest#testLauncherCallbackPackageRemovedMainUser",
+    "com.android.cts.devicepolicy.LauncherAppsSingleUserTest#testLauncherCallbackPackageChangedMainUser",
+    "com.android.cts.devicepolicy.LauncherAppsSingleUserTest#testLauncherNonExportedAppFails",
+    "com.android.cts.devicepolicy.LauncherAppsSingleUserTest#testLaunchNonExportActivityFails",
+    "com.android.cts.devicepolicy.LauncherAppsSingleUserTest#testLaunchMainActivity"
   ]
 },
 {
-  description: "Flaky test which ocassionally fails",
-  names: [
-    "com.android.cts.devicepolicy.DeviceOwnerTest#testLockTask"
-  ],
-  bug: 17890673
-},
-{
-
   description: "These tests fail on some devices.",
   names: [
     "android.uirendering.cts.testclasses.ExactCanvasTests#testBlueRect",
diff --git a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
index 39b116a..b11248a 100644
--- a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
+++ b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
@@ -342,6 +342,7 @@
                             (NotificationManager) getActivity().getSystemService(
                                     Service.NOTIFICATION_SERVICE);
                         notificationManager.notify(notificationId, notification);
+                        getActivity().finish();
                     }
                 });
             }},
diff --git a/tests/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java b/tests/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
index d0255f3..1f61b27 100644
--- a/tests/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
+++ b/tests/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
@@ -21,7 +21,11 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.os.Build;
 import android.os.UserManager;
 import android.provider.Settings;
 import android.test.AndroidTestCase;
@@ -44,6 +48,8 @@
     private ComponentName mComponent;
     private ComponentName mSecondComponent;
     private boolean mDeviceAdmin;
+    private boolean mManagedProfiles;
+    private PackageManager mPackageManager;
 
     private static final String TEST_CA_STRING1 =
             "-----BEGIN CERTIFICATE-----\n" +
@@ -68,9 +74,11 @@
         mDevicePolicyManager = (DevicePolicyManager)
                 mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
         mComponent = DeviceAdminInfoTest.getReceiverComponent();
+        mPackageManager = mContext.getPackageManager();
         mSecondComponent = DeviceAdminInfoTest.getSecondReceiverComponent();
-        mDeviceAdmin =
-                mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN);
+        mDeviceAdmin = mPackageManager.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN);
+        mManagedProfiles = mDeviceAdmin
+                && mPackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS);
         setBlankPassword();
     }
 
@@ -894,6 +902,32 @@
         }
     }
 
+    /**
+     * Test whether the version of the pre-installed launcher is at least L. This is needed for
+     * managed profile support.
+     */
+    public void testLauncherVersionAtLeastL() throws Exception {
+        if (!mManagedProfiles) {
+            return;
+        }
+
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.addCategory(Intent.CATEGORY_HOME);
+        List<ResolveInfo> resolveInfos = mPackageManager.queryIntentActivities(intent,
+                0 /* default flags */);
+        assertFalse("No launcher present", resolveInfos.isEmpty());
+
+        for (ResolveInfo resolveInfo : resolveInfos) {
+            ApplicationInfo launcherAppInfo = mPackageManager.getApplicationInfo(
+                    resolveInfo.activityInfo.packageName, 0 /* default flags */);
+            if ((launcherAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 &&
+                    launcherAppInfo.targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
+                return;
+            }
+        }
+        fail("No system launcher with version L+ present present on device.");
+    }
+
     private void assertDeviceOwnerMessage(String message) {
         assertTrue("message is: "+ message, message.contains("does not own the device")
                 || message.contains("can only be called by the device owner"));
diff --git a/tests/tests/app/src/android/app/cts/ActivityManagerMemoryClassTest.java b/tests/tests/app/src/android/app/cts/ActivityManagerMemoryClassTest.java
index 7d3fc5e..904a056 100644
--- a/tests/tests/app/src/android/app/cts/ActivityManagerMemoryClassTest.java
+++ b/tests/tests/app/src/android/app/cts/ActivityManagerMemoryClassTest.java
@@ -24,6 +24,8 @@
 import android.util.DisplayMetrics;
 import android.view.Display;
 import android.view.WindowManager;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * {@link ActivityInstrumentationTestCase2} that tests {@link ActivityManager#getMemoryClass()}
@@ -37,6 +39,66 @@
         super(ActivityManagerMemoryClassLaunchActivity.class);
     }
 
+    public static class ExpectedMemorySizesClass {
+        private static final Map<Integer, Integer> expectedMemorySizeForSmallNormalScreen
+            =  new HashMap<Integer, Integer>();
+        private static final Map<Integer, Integer> expectedMemorySizeForLargeScreen
+            =  new HashMap<Integer, Integer>();
+        private static final Map<Integer, Integer> expectedMemorySizeForXLargeScreen
+            =  new HashMap<Integer, Integer>();
+
+        static {
+            expectedMemorySizeForSmallNormalScreen.put(DisplayMetrics.DENSITY_LOW, 32);
+            expectedMemorySizeForSmallNormalScreen.put(DisplayMetrics.DENSITY_MEDIUM, 32);
+            expectedMemorySizeForSmallNormalScreen.put(DisplayMetrics.DENSITY_TV, 48);
+            expectedMemorySizeForSmallNormalScreen.put(DisplayMetrics.DENSITY_HIGH, 48);
+            expectedMemorySizeForSmallNormalScreen.put(DisplayMetrics.DENSITY_XHIGH, 48);
+            expectedMemorySizeForSmallNormalScreen.put(DisplayMetrics.DENSITY_400, 96);
+            expectedMemorySizeForSmallNormalScreen.put(DisplayMetrics.DENSITY_XXHIGH, 128);
+            expectedMemorySizeForSmallNormalScreen.put(DisplayMetrics.DENSITY_560, 192);
+            expectedMemorySizeForSmallNormalScreen.put(DisplayMetrics.DENSITY_XXXHIGH, 256);
+        }
+
+        static {
+            expectedMemorySizeForLargeScreen.put(DisplayMetrics.DENSITY_LOW, 32);
+            expectedMemorySizeForLargeScreen.put(DisplayMetrics.DENSITY_MEDIUM, 64);
+            expectedMemorySizeForLargeScreen.put(DisplayMetrics.DENSITY_TV, 80);
+            expectedMemorySizeForLargeScreen.put(DisplayMetrics.DENSITY_HIGH, 80);
+            expectedMemorySizeForLargeScreen.put(DisplayMetrics.DENSITY_XHIGH, 128);
+            expectedMemorySizeForLargeScreen.put(DisplayMetrics.DENSITY_400, 192);
+            expectedMemorySizeForLargeScreen.put(DisplayMetrics.DENSITY_XXHIGH, 256);
+            expectedMemorySizeForLargeScreen.put(DisplayMetrics.DENSITY_560, 384);
+            expectedMemorySizeForLargeScreen.put(DisplayMetrics.DENSITY_XXXHIGH, 512);
+        }
+
+        static {
+            expectedMemorySizeForXLargeScreen.put(DisplayMetrics.DENSITY_LOW, 48);
+            expectedMemorySizeForXLargeScreen.put(DisplayMetrics.DENSITY_MEDIUM, 80);
+            expectedMemorySizeForXLargeScreen.put(DisplayMetrics.DENSITY_TV, 96);
+            expectedMemorySizeForXLargeScreen.put(DisplayMetrics.DENSITY_HIGH, 96);
+            expectedMemorySizeForXLargeScreen.put(DisplayMetrics.DENSITY_XHIGH, 192);
+            expectedMemorySizeForXLargeScreen.put(DisplayMetrics.DENSITY_400, 288);
+            expectedMemorySizeForXLargeScreen.put(DisplayMetrics.DENSITY_XXHIGH, 384);
+            expectedMemorySizeForXLargeScreen.put(DisplayMetrics.DENSITY_560, 576);
+            expectedMemorySizeForXLargeScreen.put(DisplayMetrics.DENSITY_XXXHIGH, 768);
+        }
+
+        public static Integer getExpectedMemorySize(int screenSize, int screenDensity) {
+           switch (screenSize) {
+                case Configuration.SCREENLAYOUT_SIZE_SMALL:
+                case Configuration.SCREENLAYOUT_SIZE_NORMAL:
+                    return expectedMemorySizeForSmallNormalScreen.get(screenDensity);
+                case Configuration.SCREENLAYOUT_SIZE_LARGE:
+                    return expectedMemorySizeForLargeScreen.get(screenDensity);
+                case Configuration.SCREENLAYOUT_SIZE_XLARGE:
+                    return expectedMemorySizeForXLargeScreen.get(screenDensity);
+                default:
+                    throw new IllegalArgumentException("No memory requirement specified "
+                        + " for screen layout size " + screenSize);
+           }
+        }
+    }
+
     public void testGetMemoryClass() throws Exception {
         int memoryClass = getMemoryClass();
         int screenDensity = getScreenDensity();
@@ -70,46 +132,8 @@
     }
 
     private void assertMemoryForScreenDensity(int memoryClass, int screenDensity, int screenSize) {
-        int expectedMinimumMemory = -1;
-        boolean isXLarge = screenSize == Configuration.SCREENLAYOUT_SIZE_XLARGE;
-        switch (screenDensity) {
-            case DisplayMetrics.DENSITY_LOW:
-                expectedMinimumMemory = 16;
-                break;
-
-            case DisplayMetrics.DENSITY_MEDIUM:
-                expectedMinimumMemory = isXLarge ? 32 : 16;
-                break;
-
-            case DisplayMetrics.DENSITY_HIGH:
-            case DisplayMetrics.DENSITY_TV:
-                expectedMinimumMemory = isXLarge ? 64 : 32;
-                break;
-
-            case DisplayMetrics.DENSITY_XHIGH:
-                expectedMinimumMemory = isXLarge ? 128 : 64;
-                break;
-
-            case DisplayMetrics.DENSITY_400:
-                expectedMinimumMemory = isXLarge ? 192 : 128;
-                break;
-
-            case DisplayMetrics.DENSITY_XXHIGH:
-                expectedMinimumMemory = isXLarge ? 256 : 128;
-                break;
-
-            case DisplayMetrics.DENSITY_560:
-                expectedMinimumMemory = isXLarge ? 512 : 256;
-                break;
-
-            case DisplayMetrics.DENSITY_XXXHIGH:
-                expectedMinimumMemory = isXLarge ? 512 : 256;
-                break;
-
-            default:
-                throw new IllegalArgumentException("No memory requirement specified "
-                        + " for screen density " + screenDensity);
-        }
+        int expectedMinimumMemory = ExpectedMemorySizesClass.getExpectedMemorySize(screenSize,
+                                                                                   screenDensity);
 
         assertTrue("Expected to have at least " + expectedMinimumMemory
                 + "mb of memory for screen density " + screenDensity,
diff --git a/tests/tests/app/src/android/app/cts/DownloadManagerTest.java b/tests/tests/app/src/android/app/cts/DownloadManagerTest.java
index 84faffa..a68d860 100644
--- a/tests/tests/app/src/android/app/cts/DownloadManagerTest.java
+++ b/tests/tests/app/src/android/app/cts/DownloadManagerTest.java
@@ -49,7 +49,7 @@
     private static final int MINIMUM_DOWNLOAD_BYTES = 100 * 1024 * 1024;
 
     private static final long SHORT_TIMEOUT = 5 * DateUtils.SECOND_IN_MILLIS;
-    private static final long LONG_TIMEOUT = 2 * DateUtils.MINUTE_IN_MILLIS;
+    private static final long LONG_TIMEOUT = 3 * DateUtils.MINUTE_IN_MILLIS;
 
     private DownloadManager mDownloadManager;
 
diff --git a/tests/tests/app/src/android/app/cts/SystemFeaturesTest.java b/tests/tests/app/src/android/app/cts/SystemFeaturesTest.java
index 165e67b..4e57d31 100644
--- a/tests/tests/app/src/android/app/cts/SystemFeaturesTest.java
+++ b/tests/tests/app/src/android/app/cts/SystemFeaturesTest.java
@@ -32,6 +32,9 @@
 import android.hardware.SensorManager;
 import android.hardware.Camera.CameraInfo;
 import android.hardware.Camera.Parameters;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CameraMetadata;
 import android.location.LocationManager;
 import android.net.sip.SipManager;
 import android.net.wifi.WifiManager;
@@ -59,6 +62,7 @@
     private SensorManager mSensorManager;
     private TelephonyManager mTelephonyManager;
     private WifiManager mWifiManager;
+    private CameraManager mCameraManager;
 
     @Override
     protected void setUp() throws Exception {
@@ -77,6 +81,7 @@
         mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
         mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
         mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+        mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
     }
 
     /**
@@ -106,7 +111,7 @@
         }
     }
 
-    public void testCameraFeatures() {
+    public void testCameraFeatures() throws Exception {
         int numCameras = Camera.getNumberOfCameras();
         if (numCameras == 0) {
             assertNotAvailable(PackageManager.FEATURE_CAMERA);
@@ -114,6 +119,11 @@
             assertNotAvailable(PackageManager.FEATURE_CAMERA_FLASH);
             assertNotAvailable(PackageManager.FEATURE_CAMERA_FRONT);
             assertNotAvailable(PackageManager.FEATURE_CAMERA_ANY);
+            assertNotAvailable(PackageManager.FEATURE_CAMERA_LEVEL_FULL);
+            assertNotAvailable(PackageManager.FEATURE_CAMERA_CAPABILITY_MANUAL_SENSOR);
+            assertNotAvailable(PackageManager.FEATURE_CAMERA_CAPABILITY_MANUAL_POST_PROCESSING);
+            assertNotAvailable(PackageManager.FEATURE_CAMERA_CAPABILITY_RAW);
+
             assertFalse("Devices supporting external cameras must have a representative camera " +
                     "connected for testing",
                     mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_EXTERNAL));
@@ -121,9 +131,48 @@
             assertAvailable(PackageManager.FEATURE_CAMERA_ANY);
             checkFrontCamera();
             checkRearCamera();
+            checkCamera2Features();
         }
     }
 
+    private void checkCamera2Features() throws Exception {
+        String[] cameraIds = mCameraManager.getCameraIdList();
+        boolean fullCamera = false;
+        boolean manualSensor = false;
+        boolean manualPostProcessing = false;
+        boolean raw = false;
+        CameraCharacteristics[] cameraChars = new CameraCharacteristics[cameraIds.length];
+        for (String cameraId : cameraIds) {
+            CameraCharacteristics chars = mCameraManager.getCameraCharacteristics(cameraId);
+            Integer hwLevel = chars.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
+            int[] capabilities = chars.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
+            if (hwLevel == CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_FULL) {
+                fullCamera = true;
+            }
+            for (int capability : capabilities) {
+                switch (capability) {
+                    case CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR:
+                        manualSensor = true;
+                        break;
+                    case CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING:
+                        manualPostProcessing = true;
+                        break;
+                    case CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW:
+                        raw = true;
+                        break;
+                    default:
+                        // Capabilities don't have a matching system feature
+                        break;
+                }
+            }
+        }
+        assertFeature(fullCamera, PackageManager.FEATURE_CAMERA_LEVEL_FULL);
+        assertFeature(manualSensor, PackageManager.FEATURE_CAMERA_CAPABILITY_MANUAL_SENSOR);
+        assertFeature(manualPostProcessing,
+                PackageManager.FEATURE_CAMERA_CAPABILITY_MANUAL_POST_PROCESSING);
+        assertFeature(raw, PackageManager.FEATURE_CAMERA_CAPABILITY_RAW);
+    }
+
     private void checkFrontCamera() {
         CameraInfo info = new CameraInfo();
         int numCameras = Camera.getNumberOfCameras();
@@ -243,15 +292,58 @@
                 Sensor.TYPE_STEP_COUNTER);
         assertFeatureForSensor(featuresLeft, PackageManager.FEATURE_SENSOR_STEP_DETECTOR,
                 Sensor.TYPE_STEP_DETECTOR);
-        assertFeatureForSensor(featuresLeft, PackageManager.FEATURE_SENSOR_HEART_RATE,
-                Sensor.TYPE_HEART_RATE);
-        assertFeatureForSensor(featuresLeft, PackageManager.FEATURE_SENSOR_HEART_RATE_ECG,
-                Sensor.TYPE_HEART_RATE);
         assertFeatureForSensor(featuresLeft, PackageManager.FEATURE_SENSOR_AMBIENT_TEMPERATURE,
                 Sensor.TYPE_AMBIENT_TEMPERATURE);
         assertFeatureForSensor(featuresLeft, PackageManager.FEATURE_SENSOR_RELATIVE_HUMIDITY,
                 Sensor.TYPE_RELATIVE_HUMIDITY);
 
+
+        /*
+         * We have three cases to test for :
+         * Case 1:  Device does not have an HRM
+         * FEATURE_SENSOR_HEART_RATE               false
+         * FEATURE_SENSOR_HEART_RATE_ECG           false
+         * assertFeatureForSensor(TYPE_HEART_RATE) false
+         *
+         * Case 2:  Device has a PPG HRM
+         * FEATURE_SENSOR_HEART_RATE               true
+         * FEATURE_SENSOR_HEART_RATE_ECG           false
+         * assertFeatureForSensor(TYPE_HEART_RATE) true
+         *
+         * Case 3:  Device has an ECG HRM
+         * FEATURE_SENSOR_HEART_RATE               false
+         * FEATURE_SENSOR_HEART_RATE_ECG           true
+         * assertFeatureForSensor(TYPE_HEART_RATE) true
+         */
+
+        if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_SENSOR_HEART_RATE_ECG)) {
+                /* Case 3 for FEATURE_SENSOR_HEART_RATE_ECG true case */
+                assertFeatureForSensor(featuresLeft, PackageManager.FEATURE_SENSOR_HEART_RATE_ECG,
+                        Sensor.TYPE_HEART_RATE);
+
+                /* Remove HEART_RATE from featuresLeft, no way to test that one */
+                assertTrue("Features left " + featuresLeft + " to check did not include "
+                        + PackageManager.FEATURE_SENSOR_HEART_RATE,
+                        featuresLeft.remove(PackageManager.FEATURE_SENSOR_HEART_RATE));
+        } else if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_SENSOR_HEART_RATE)) {
+                /* Case 1 & 2 for FEATURE_SENSOR_HEART_RATE_ECG false case */
+                assertFeatureForSensor(featuresLeft, PackageManager.FEATURE_SENSOR_HEART_RATE_ECG,
+                        Sensor.TYPE_HEART_RATE);
+
+                /* Case 1 & 3 for FEATURE_SENSOR_HEART_RATE false case */
+                assertFeatureForSensor(featuresLeft, PackageManager.FEATURE_SENSOR_HEART_RATE,
+                        Sensor.TYPE_HEART_RATE);
+        } else {
+                /* Case 2 for FEATURE_SENSOR_HEART_RATE true case */
+                assertFeatureForSensor(featuresLeft, PackageManager.FEATURE_SENSOR_HEART_RATE,
+                        Sensor.TYPE_HEART_RATE);
+
+                /* Remove HEART_RATE_ECG from featuresLeft, no way to test that one */
+                assertTrue("Features left " + featuresLeft + " to check did not include "
+                        + PackageManager.FEATURE_SENSOR_HEART_RATE_ECG,
+                        featuresLeft.remove(PackageManager.FEATURE_SENSOR_HEART_RATE_ECG));
+        }
+
         assertTrue("Assertions need to be added to this test for " + featuresLeft,
                 featuresLeft.isEmpty());
     }
@@ -400,4 +492,12 @@
         assertFalse("PackageManager#getSystemAvailableFeatures should NOT have " + feature,
                 mAvailableFeatures.contains(feature));
     }
+
+    private void assertFeature(boolean exist, String feature) {
+        if (exist) {
+            assertAvailable(feature);
+        } else {
+            assertNotAvailable(feature);
+        }
+    }
 }
diff --git a/tests/tests/display/src/android/display/cts/VirtualDisplayTest.java b/tests/tests/display/src/android/display/cts/VirtualDisplayTest.java
index f2f859a..872de91 100644
--- a/tests/tests/display/src/android/display/cts/VirtualDisplayTest.java
+++ b/tests/tests/display/src/android/display/cts/VirtualDisplayTest.java
@@ -57,7 +57,7 @@
     private static final int WIDTH = 720;
     private static final int HEIGHT = 480;
     private static final int DENSITY = DisplayMetrics.DENSITY_MEDIUM;
-    private static final int TIMEOUT = 10000;
+    private static final int TIMEOUT = 40000;
 
     // Colors that we use as a signal to determine whether some desired content was
     // drawn.  The colors themselves doesn't matter but we choose them to have with distinct
diff --git a/tests/tests/hardware/AndroidManifest.xml b/tests/tests/hardware/AndroidManifest.xml
index 1a02d0a..ab81162 100644
--- a/tests/tests/hardware/AndroidManifest.xml
+++ b/tests/tests/hardware/AndroidManifest.xml
@@ -24,6 +24,7 @@
     <uses-permission android:name="android.permission.WAKE_LOCK" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.BODY_SENSORS" />
+    <uses-permission android:name="android.permission.TRANSMIT_IR" />
 
     <application>
         <uses-library android:name="android.test.runner" />
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureRequestTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureRequestTest.java
index 5346ae1..bb5527f 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureRequestTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureRequestTest.java
@@ -326,7 +326,13 @@
                 flashTestByAeMode(listener, CaptureRequest.CONTROL_AE_MODE_ON);
 
                 // LEGACY won't support AE mode OFF
-                if (mStaticInfo.isHardwareLevelLimitedOrBetter()) {
+                boolean aeOffModeSupported = false;
+                for (int aeMode : mStaticInfo.getAeAvailableModesChecked()) {
+                    if (aeMode == CaptureRequest.CONTROL_AE_MODE_OFF) {
+                        aeOffModeSupported = true;
+                    }
+                }
+                if (aeOffModeSupported) {
                     flashTestByAeMode(listener, CaptureRequest.CONTROL_AE_MODE_OFF);
                 }
 
@@ -2063,6 +2069,16 @@
      */
     private void verifyFpsNotSlowDown(CaptureRequest.Builder requestBuilder,
             int numFramesVerified)  throws Exception {
+        boolean frameDurationAvailable = true;
+        // Allow a few frames for AE to settle on target FPS range
+        final int NUM_FRAME_TO_SKIP = 6;
+        float frameDurationErrorMargin = FRAME_DURATION_ERROR_MARGIN;
+        if (!mStaticInfo.areKeysAvailable(CaptureResult.SENSOR_FRAME_DURATION)) {
+            frameDurationAvailable = false;
+            // Allow a larger error margin (1.5%) for timestamps
+            frameDurationErrorMargin = 0.015f;
+        }
+
         Range<Integer>[] fpsRanges = mStaticInfo.getAeAvailableTargetFpsRangesChecked();
         boolean antiBandingOffIsSupported = mStaticInfo.isAntiBandingOffModeSupported();
         Range<Integer> fpsRange;
@@ -2102,18 +2118,34 @@
 
             resultListener = new SimpleCaptureCallback();
             startPreview(requestBuilder, previewSz, resultListener);
-            long[] frameDurationRange =
-                    new long[]{(long) (1e9 / fpsRange.getUpper()), (long) (1e9 / fpsRange.getLower())};
+            waitForSettingsApplied(resultListener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
+            // Wait several more frames for AE to settle on target FPS range
+            waitForNumResults(resultListener, NUM_FRAME_TO_SKIP);
+
+            long[] frameDurationRange = new long[]{
+                    (long) (1e9 / fpsRange.getUpper()), (long) (1e9 / fpsRange.getLower())};
+            long captureTime = 0, prevCaptureTime = 0;
             for (int j = 0; j < numFramesVerified; j++) {
+                long frameDuration = frameDurationRange[0];
                 CaptureResult result =
                         resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
                 validatePipelineDepth(result);
-                long frameDuration = getValueNotNull(result, CaptureResult.SENSOR_FRAME_DURATION);
+                if (frameDurationAvailable) {
+                    frameDuration = getValueNotNull(result, CaptureResult.SENSOR_FRAME_DURATION);
+                } else {
+                    // if frame duration is not available, check timestamp instead
+                    captureTime = getValueNotNull(result, CaptureResult.SENSOR_TIMESTAMP);
+                    if (j > 0) {
+                        frameDuration = captureTime - prevCaptureTime;
+                    }
+                    prevCaptureTime = captureTime;
+                }
                 mCollector.expectInRange(
-                        "Frame duration must be in the range of " + Arrays.toString(frameDurationRange),
+                        "Frame duration must be in the range of " +
+                                Arrays.toString(frameDurationRange),
                         frameDuration,
-                        (long) (frameDurationRange[0] * (1 - FRAME_DURATION_ERROR_MARGIN)),
-                        (long) (frameDurationRange[1] * (1 + FRAME_DURATION_ERROR_MARGIN)));
+                        (long) (frameDurationRange[0] * (1 - frameDurationErrorMargin)),
+                        (long) (frameDurationRange[1] * (1 + frameDurationErrorMargin)));
             }
         }
 
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
index 1a00d9e..f08b60a 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
@@ -75,6 +75,8 @@
             CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE;
     private static final int MANUAL_SENSOR =
             CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR;
+    private static final int MANUAL_POSTPROC =
+            CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING;
     private static final int RAW =
             CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW;
 
@@ -248,8 +250,8 @@
                 expectKeyAvailable(c, CameraCharacteristics.STATISTICS_INFO_AVAILABLE_HOT_PIXEL_MAP_MODES   , OPT      ,   RAW                  );
                 expectKeyAvailable(c, CameraCharacteristics.STATISTICS_INFO_MAX_FACE_COUNT                  , LEGACY   ,   BC                   );
                 expectKeyAvailable(c, CameraCharacteristics.SYNC_MAX_LATENCY                                , LEGACY   ,   BC                   );
-                expectKeyAvailable(c, CameraCharacteristics.TONEMAP_AVAILABLE_TONE_MAP_MODES                , FULL     ,   MANUAL_SENSOR        );
-                expectKeyAvailable(c, CameraCharacteristics.TONEMAP_MAX_CURVE_POINTS                        , FULL     ,   MANUAL_SENSOR        );
+                expectKeyAvailable(c, CameraCharacteristics.TONEMAP_AVAILABLE_TONE_MAP_MODES                , FULL     ,   MANUAL_POSTPROC      );
+                expectKeyAvailable(c, CameraCharacteristics.TONEMAP_MAX_CURVE_POINTS                        , FULL     ,   MANUAL_POSTPROC      );
 
                 // Future: Use column editors for modifying above, ignore line length to keep 1 key per line
 
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/RecordingTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/RecordingTest.java
index 90cb18a..21920b7 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/RecordingTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/RecordingTest.java
@@ -30,6 +30,7 @@
 import android.media.Image;
 import android.media.ImageReader;
 import android.media.MediaCodecList;
+import android.media.MediaFormat;
 import android.media.MediaPlayer;
 import android.media.MediaRecorder;
 import android.os.Environment;
@@ -58,7 +59,7 @@
     private static final boolean DEBUG_DUMP = Log.isLoggable(TAG, Log.DEBUG);
     private static final int RECORDING_DURATION_MS = 3000;
     private static final int DURATION_MARGIN_MS = 600;
-    private static final int FRAME_DURATION_ERROR_TOLERANCE_MS = 3;
+    private static final double FRAME_DURATION_ERROR_TOLERANCE_MS = 3.0;
     private static final int BIT_RATE_1080P = 16000000;
     private static final int BIT_RATE_MIN = 64000;
     private static final int BIT_RATE_MAX = 40000000;
@@ -78,6 +79,7 @@
     private static final int MAX_VIDEO_SNAPSHOT_IMAGES = 5;
     private static final int BURST_VIDEO_SNAPSHOT_NUM = 3;
     private static final int SLOWMO_SLOW_FACTOR = 4;
+    private static final int MAX_NUM_FRAME_DROP_INTERVAL_ALLOWED = 4;
     private List<Size> mSupportedVideoSizes;
     private Surface mRecordingSurface;
     private MediaRecorder mMediaRecorder;
@@ -384,6 +386,7 @@
      * given camera. preview size is set to the video size.
      */
     private void basicRecordingTestByCamera() throws Exception {
+        Size maxPreviewSize = mOrderedPreviewSizes.get(0);
         for (int profileId : mCamcorderProfileList) {
             int cameraId = Integer.valueOf(mCamera.getId());
             if (!CamcorderProfile.hasProfile(cameraId, profileId) ||
@@ -393,6 +396,12 @@
 
             CamcorderProfile profile = CamcorderProfile.get(cameraId, profileId);
             Size videoSz = new Size(profile.videoFrameWidth, profile.videoFrameHeight);
+            if (mStaticInfo.isHardwareLevelLegacy() &&
+                    (videoSz.getWidth() > maxPreviewSize.getWidth() ||
+                     videoSz.getHeight() > maxPreviewSize.getHeight())) {
+                // Skip. Legacy mode can only do recording up to max preview size
+                continue;
+            }
             assertTrue("Video size " + videoSz.toString() + " for profile ID " + profileId +
                             " must be one of the camera device supported video size!",
                             mSupportedVideoSizes.contains(videoSz));
@@ -891,7 +900,7 @@
      */
     private int validateFrameDropAroundVideoSnapshot(
             SimpleCaptureCallback resultListener, long imageTimeStamp) {
-        int expectedDurationMs = 1000 / mVideoFrameRate;
+        double expectedDurationMs = 1000.0 / mVideoFrameRate;
         CaptureResult prevResult = resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
         long prevTS = getValueNotNull(prevResult, CaptureResult.SENSOR_TIMESTAMP);
         while (!resultListener.hasMoreResults()) {
@@ -903,23 +912,41 @@
                 CaptureResult nextResult =
                         resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
                 long nextTS = getValueNotNull(nextResult, CaptureResult.SENSOR_TIMESTAMP);
-                int durationMs = (int) (currentTS - prevTS) / 1000000;
+                double durationMs = (currentTS - prevTS) / 1000000.0;
                 int totalFramesDropped = 0;
 
                 // Snapshots in legacy mode pause the preview briefly.  Skip the duration
                 // requirements for legacy mode unless this is fixed.
                 if (!mStaticInfo.isHardwareLevelLegacy()) {
+                    mCollector.expectTrue(
+                            String.format(
+                                    "Video %dx%d Frame drop detected before video snapshot: " +
+                                            "duration %.2fms (expected %.2fms)",
+                                    mVideoSize.getWidth(), mVideoSize.getHeight(),
+                                    durationMs, expectedDurationMs
+                            ),
+                            durationMs <= (expectedDurationMs * MAX_NUM_FRAME_DROP_INTERVAL_ALLOWED)
+                    );
                     // Log a warning is there is any frame drop detected.
                     if (durationMs >= expectedDurationMs * 2) {
                         Log.w(TAG, String.format(
                                 "Video %dx%d Frame drop detected before video snapshot: " +
-                                        "duration %dms (expected %dms)",
+                                        "duration %.2fms (expected %.2fms)",
                                 mVideoSize.getWidth(), mVideoSize.getHeight(),
                                 durationMs, expectedDurationMs
                         ));
                     }
 
                     durationMs = (int) (nextTS - currentTS) / 1000000;
+                    mCollector.expectTrue(
+                            String.format(
+                                    "Video %dx%d Frame drop detected after video snapshot: " +
+                                            "duration %.2fms (expected %.2fms)",
+                                    mVideoSize.getWidth(), mVideoSize.getHeight(),
+                                    durationMs, expectedDurationMs
+                            ),
+                            durationMs <= (expectedDurationMs * MAX_NUM_FRAME_DROP_INTERVAL_ALLOWED)
+                    );
                     // Log a warning is there is any frame drop detected.
                     if (durationMs >= expectedDurationMs * 2) {
                         Log.w(TAG, String.format(
@@ -930,10 +957,9 @@
                         ));
                     }
 
-                    int totalDurationMs = (int) (nextTS - prevTS) / 1000000;
-                    // Rounding and minus 2 for the expected 2 frames interval
-                    totalFramesDropped =
-                            (totalDurationMs + expectedDurationMs / 2) /expectedDurationMs - 2;
+                    double totalDurationMs = (nextTS - prevTS) / 1000000.0;
+                    // Minus 2 for the expected 2 frames interval
+                    totalFramesDropped = (int) (totalDurationMs / expectedDurationMs) - 2;
                     if (totalFramesDropped < 0) {
                         Log.w(TAG, "totalFrameDropped is " + totalFramesDropped +
                                 ". Video frame rate might be too fast.");
@@ -952,19 +978,19 @@
      * Validate frame jittering from the input simple listener's buffered results
      */
     private void validateJittering(SimpleCaptureCallback resultListener) {
-        int expectedDurationMs = 1000 / mVideoFrameRate;
+        double expectedDurationMs = 1000.0 / mVideoFrameRate;
         CaptureResult prevResult = resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
         long prevTS = getValueNotNull(prevResult, CaptureResult.SENSOR_TIMESTAMP);
         while (!resultListener.hasMoreResults()) {
             CaptureResult currentResult =
                     resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
             long currentTS = getValueNotNull(currentResult, CaptureResult.SENSOR_TIMESTAMP);
-            int durationMs = (int) (currentTS - prevTS) / 1000000;
-            int durationError = Math.abs(durationMs - expectedDurationMs);
+            double durationMs = (currentTS - prevTS) / 1000000.0;
+            double durationError = Math.abs(durationMs - expectedDurationMs);
             long frameNumber = currentResult.getFrameNumber();
             mCollector.expectTrue(
                     String.format(
-                            "Resolution %dx%d Frame %d: jittering (%dms) exceeds bound [%dms,%dms]",
+                            "Resolution %dx%d Frame %d: jittering (%.2fms) exceeds bound [%.2fms,%.2fms]",
                             mVideoSize.getWidth(), mVideoSize.getHeight(),
                             frameNumber, durationMs,
                             expectedDurationMs - FRAME_DURATION_ERROR_TOLERANCE_MS,
@@ -1031,133 +1057,10 @@
      * by AVC specification for certain level.
      */
     private static boolean isSupportedByAVCEncoder(Size sz, int frameRate) {
-        String mimeType = "video/avc";
-        MediaCodecInfo codecInfo = getEncoderInfo(mimeType);
-        if (codecInfo == null) {
-            return false;
-        }
-        CodecCapabilities cap = codecInfo.getCapabilitiesForType(mimeType);
-        if (cap == null) {
-            return false;
-        }
-
-        int highestLevel = 0;
-        for (CodecProfileLevel lvl : cap.profileLevels) {
-            if (lvl.level > highestLevel) {
-                highestLevel = lvl.level;
-            }
-        }
-        // Don't support anything meaningful for level 1 or 2.
-        if (highestLevel <= CodecProfileLevel.AVCLevel2) {
-            return false;
-        }
-
-        if(VERBOSE) {
-            Log.v(TAG, "The highest level supported by encoder is: " + highestLevel);
-        }
-
-        // Put bitRate here for future use.
-        int maxW, maxH, bitRate;
-        // Max encoding speed.
-        int maxMacroblocksPerSecond = 0;
-        switch(highestLevel) {
-            case CodecProfileLevel.AVCLevel21:
-                maxW = 352;
-                maxH = 576;
-                bitRate = 4000000;
-                maxMacroblocksPerSecond = 19800;
-                break;
-            case CodecProfileLevel.AVCLevel22:
-                maxW = 720;
-                maxH = 480;
-                bitRate = 4000000;
-                maxMacroblocksPerSecond = 20250;
-                break;
-            case CodecProfileLevel.AVCLevel3:
-                maxW = 720;
-                maxH = 480;
-                bitRate = 10000000;
-                maxMacroblocksPerSecond = 40500;
-                break;
-            case CodecProfileLevel.AVCLevel31:
-                maxW = 1280;
-                maxH = 720;
-                bitRate = 14000000;
-                maxMacroblocksPerSecond = 108000;
-                break;
-            case CodecProfileLevel.AVCLevel32:
-                maxW = 1280;
-                maxH = 720;
-                bitRate = 20000000;
-                maxMacroblocksPerSecond = 216000;
-                break;
-            case CodecProfileLevel.AVCLevel4:
-                maxW = 1920;
-                maxH = 1088; // It should be 1088 in terms of AVC capability.
-                bitRate = 20000000;
-                maxMacroblocksPerSecond = 245760;
-                break;
-            case CodecProfileLevel.AVCLevel41:
-                maxW = 1920;
-                maxH = 1088; // It should be 1088 in terms of AVC capability.
-                bitRate = 50000000;
-                maxMacroblocksPerSecond = 245760;
-                break;
-            case CodecProfileLevel.AVCLevel42:
-                maxW = 2048;
-                maxH = 1088; // It should be 1088 in terms of AVC capability.
-                bitRate = 50000000;
-                maxMacroblocksPerSecond = 522240;
-                break;
-            case CodecProfileLevel.AVCLevel5:
-                maxW = 3672;
-                maxH = 1536;
-                bitRate = 135000000;
-                maxMacroblocksPerSecond = 589824;
-                break;
-            case CodecProfileLevel.AVCLevel51:
-            default:
-                maxW = 4096;
-                maxH = 2304;
-                bitRate = 240000000;
-                maxMacroblocksPerSecond = 983040;
-                break;
-        }
-
-        // Check size limit.
-        if (sz.getWidth() > maxW || sz.getHeight() > maxH) {
-            Log.i(TAG, "Requested resolution " + sz.toString() + " exceeds (" +
-                    maxW + "," + maxH + ")");
-            return false;
-        }
-
-        // Check frame rate limit.
-        Size sizeInMb = new Size((sz.getWidth() + 15) / 16, (sz.getHeight() + 15) / 16);
-        int maxFps = maxMacroblocksPerSecond / (sizeInMb.getWidth() * sizeInMb.getHeight());
-        if (frameRate > maxFps) {
-            Log.i(TAG, "Requested frame rate " + frameRate + " exceeds " + maxFps);
-            return false;
-        }
-
-        return true;
-    }
-
-    private static MediaCodecInfo getEncoderInfo(String mimeType) {
-        int numCodecs = MediaCodecList.getCodecCount();
-        for (int i = 0; i < numCodecs; i++) {
-            MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
-
-            if (!codecInfo.isEncoder()) {
-                continue;
-            }
-
-            String[] types = codecInfo.getSupportedTypes();
-            for (int j = 0; j < types.length; j++) {
-                if (types[j].equalsIgnoreCase(mimeType)) {
-                    return codecInfo;
-                }
-            }
-        }
-        return null;
+        MediaFormat format = MediaFormat.createVideoFormat(
+                MediaFormat.MIMETYPE_VIDEO_AVC, sz.getWidth(), sz.getHeight());
+        format.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate);
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        return mcl.findEncoderForFormat(format) != null;
     }
 }
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/RobustnessTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/RobustnessTest.java
index 7960200..a6a7d10 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/RobustnessTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/RobustnessTest.java
@@ -21,7 +21,6 @@
 
 import android.graphics.ImageFormat;
 import android.graphics.SurfaceTexture;
-import android.hardware.camera2.CameraAccessException;
 import android.hardware.camera2.CameraCaptureSession;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraDevice;
@@ -33,13 +32,12 @@
 import android.hardware.camera2.cts.helpers.StaticMetadata;
 import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
 import android.media.CamcorderProfile;
+import android.media.Image;
 import android.media.ImageReader;
 import android.util.Log;
 import android.util.Size;
 import android.view.Surface;
 
-import com.android.ex.camera2.blocking.BlockingSessionCallback;
-
 import java.util.Arrays;
 import java.util.ArrayList;
 import java.util.List;
@@ -324,6 +322,16 @@
         }
     }
 
+    private final class ImageCloser implements ImageReader.OnImageAvailableListener {
+        @Override
+        public void onImageAvailable(ImageReader reader) {
+            Image image = reader.acquireLatestImage();
+            if (image != null) {
+                image.close();
+            }
+        }
+    }
+
     private void testOutputCombination(String cameraId, int[] config, MaxOutputSizes maxSizes)
             throws Exception {
 
@@ -333,6 +341,7 @@
         final int TIMEOUT_FOR_RESULT_MS = 1000;
         final int MIN_RESULT_COUNT = 3;
 
+        ImageCloser imageCloser = new ImageCloser();
         // Set up outputs
         List<Object> outputTargets = new ArrayList<>();
         List<Surface> outputSurfaces = new ArrayList<>();
@@ -358,6 +367,7 @@
                     Size targetSize = maxSizes.maxJpegSizes[sizeLimit];
                     ImageReader target = ImageReader.newInstance(
                         targetSize.getWidth(), targetSize.getHeight(), JPEG, MIN_RESULT_COUNT);
+                    target.setOnImageAvailableListener(imageCloser, mHandler);
                     outputTargets.add(target);
                     outputSurfaces.add(target.getSurface());
                     jpegTargets.add(target);
@@ -367,6 +377,7 @@
                     Size targetSize = maxSizes.maxYuvSizes[sizeLimit];
                     ImageReader target = ImageReader.newInstance(
                         targetSize.getWidth(), targetSize.getHeight(), YUV, MIN_RESULT_COUNT);
+                    target.setOnImageAvailableListener(imageCloser, mHandler);
                     outputTargets.add(target);
                     outputSurfaces.add(target.getSurface());
                     yuvTargets.add(target);
@@ -376,6 +387,7 @@
                     Size targetSize = maxSizes.maxRawSize;
                     ImageReader target = ImageReader.newInstance(
                         targetSize.getWidth(), targetSize.getHeight(), RAW, MIN_RESULT_COUNT);
+                    target.setOnImageAvailableListener(imageCloser, mHandler);
                     outputTargets.add(target);
                     outputSurfaces.add(target.getSurface());
                     rawTargets.add(target);
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/StaticMetadataCollectionTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/StaticMetadataCollectionTest.java
new file mode 100644
index 0000000..283f09b
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/StaticMetadataCollectionTest.java
@@ -0,0 +1,181 @@
+/*
+ * 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.hardware.camera2.cts;
+
+import android.content.pm.PackageManager;
+import android.cts.util.DeviceReportLog;
+import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
+import android.hardware.camera2.cts.helpers.CameraMetadataGetter;
+import android.util.Log;
+
+import com.android.cts.util.ResultType;
+import com.android.cts.util.ResultUnit;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.util.Iterator;
+
+/**
+ * This test collects camera2 API static metadata and reports to device report.
+ *
+ */
+public class StaticMetadataCollectionTest extends Camera2SurfaceViewTestCase {
+    private static final String TAG = "StaticMetadataCollectionTest";
+
+    private DeviceReportLog mReportLog;
+
+    @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.deliverReportToHost(getInstrumentation());
+        super.tearDown();
+    }
+
+    public void testDataCollection() {
+        if (hasCameraFeature()) {
+            CameraMetadataGetter cameraInfoGetter = new CameraMetadataGetter(mCameraManager);
+            for (String id : mCameraIds) {
+                // Gather camera info
+                JSONObject cameraInfo = cameraInfoGetter.getCameraInfo(id);
+                dumpJsonObjectAsCtsResult(String.format("camera2_id%s_static_info", id), cameraInfo);
+                dumpDoubleAsCtsResult(String.format("camera2_id%s_static_info:", id)
+                        + cameraInfo.toString(), 0);
+
+                JSONObject[] templates = cameraInfoGetter.getCaptureRequestTemplates(id);
+                for (int i = 0; i < templates.length; i++) {
+                    dumpJsonObjectAsCtsResult(String.format("camera2_id%s_capture_template%d",
+                            id, CameraMetadataGetter.TEMPLATE_IDS[i]), templates[i]);
+                    if (templates[i] != null) {
+                        dumpDoubleAsCtsResult(String.format("camera2_id%s_capture_template%d:",
+                                id, CameraMetadataGetter.TEMPLATE_IDS[i])
+                                + templates[i].toString(), 0);
+                    }
+                }
+            }
+
+            try {
+                cameraInfoGetter.close();
+            } catch (Exception e) {
+                Log.e(TAG, "Unable to close camera info getter " + e.getMessage());
+            }
+
+            mReportLog.printSummary("Camera data collection for static info and capture request"
+                    + " templates",
+                    0.0, ResultType.NEUTRAL, ResultUnit.NONE);
+        }
+
+    }
+
+    private void dumpDoubleAsCtsResult(String name, double value) {
+        mReportLog.printValue(name, value, ResultType.NEUTRAL, ResultUnit.NONE);
+    }
+
+    public void dumpDoubleArrayAsCtsResult(String name, double[] values) {
+        mReportLog.printArray(name, values, ResultType.NEUTRAL, ResultUnit.NONE);
+    }
+
+    private double getJsonValueAsDouble(String name, Object obj) throws Exception {
+        if (obj == null) {
+            Log.e(TAG, "Null value: " + name);
+            throw new Exception();
+        } else if (obj instanceof Double) {
+            return ((Double)obj).doubleValue();
+        } else if (obj instanceof Float) {
+            return ((Float)obj).floatValue();
+        } else if (obj instanceof Long) {
+            return ((Long)obj).longValue();
+        } else if (obj instanceof Integer) {
+            return ((Integer)obj).intValue();
+        } else if (obj instanceof Byte) {
+            return ((Byte)obj).intValue();
+        } else if (obj instanceof Short) {
+            return ((Short)obj).intValue();
+        } else if (obj instanceof Boolean) {
+            return ((Boolean)obj) ? 1 : 0;
+        } else {
+            Log.e(TAG, "Unsupported value type: " + name);
+            throw new Exception();
+        }
+    }
+
+    private void dumpJsonArrayAsCtsResult(String name, JSONArray arr) throws Exception {
+        if (arr == null || arr.length() == 0) {
+            dumpDoubleAsCtsResult(name + "[]", 0);
+        } else if (arr.get(0) instanceof JSONObject) {
+            for (int i = 0; i < arr.length(); i++) {
+                dumpJsonObjectAsCtsResult(name+String.format("[%04d]",i),(JSONObject)arr.get(i));
+            }
+        } else if (arr.get(0) instanceof JSONArray) {
+            for (int i = 0; i < arr.length(); i++) {
+                dumpJsonArrayAsCtsResult(name+String.format("[%04d]",i),(JSONArray)arr.get(i));
+            }
+        } else if (!(arr.get(0) instanceof String)) {
+            double[] values = new double[arr.length()];
+            for (int i = 0; i < arr.length(); i++) {
+                values[i] = getJsonValueAsDouble(name + "[]", arr.get(i));
+            }
+            dumpDoubleArrayAsCtsResult(name + "[]", values);
+        } else if (arr.get(0) instanceof String) {
+            for (int i = 0; i < arr.length(); i++) {
+                dumpDoubleAsCtsResult(
+                        name+String.format("[%04d]",i)+" = "+(String)arr.get(i), 0);
+            }
+        } else {
+            Log.e(TAG, "Unsupported array value type: " + name);
+            throw new Exception();
+        }
+    }
+
+    private void dumpJsonObjectAsCtsResult(String name, JSONObject obj) {
+        if (obj == null) {
+            dumpDoubleAsCtsResult(name + "{}", 0);
+            return;
+        }
+        Iterator<?> keys = obj.keys();
+        while (keys.hasNext()) {
+            try {
+                String key = (String)keys.next();
+                if (obj.get(key) instanceof JSONObject) {
+                    dumpJsonObjectAsCtsResult(name+"."+key, (JSONObject)obj.get(key));
+                } else if (obj.get(key) instanceof JSONArray) {
+                    dumpJsonArrayAsCtsResult(name+"."+key, (JSONArray)obj.get(key));
+                } else if (!(obj.get(key) instanceof String)) {
+                    dumpDoubleAsCtsResult(name+"."+key,
+                            getJsonValueAsDouble(name+"."+key, obj.get(key)));
+                } else if (obj.get(key) instanceof String) {
+                    dumpDoubleAsCtsResult(name+"."+key + " = " + (String)obj.get(key), 0);
+                } else {
+                    Log.e(TAG, "Unsupported object field type: " + name + "." + key);
+                }
+            } catch (Exception e) {
+                // Swallow
+            }
+        }
+    }
+
+    private boolean hasCameraFeature() {
+        PackageManager packageManager = getActivity().getPackageManager();
+        return packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
+    }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/StaticMetadataTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/StaticMetadataTest.java
index ec7ecf8..dd1882e 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/StaticMetadataTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/StaticMetadataTest.java
@@ -19,12 +19,15 @@
 import static android.hardware.camera2.CameraCharacteristics.*;
 
 import android.graphics.ImageFormat;
+import android.graphics.Rect;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.CameraCharacteristics.Key;
 import android.hardware.camera2.cts.helpers.StaticMetadata;
 import android.hardware.camera2.cts.helpers.StaticMetadata.CheckLevel;
 import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
+import android.hardware.camera2.params.StreamConfigurationMap;
 import android.util.Log;
 import android.util.Size;
 
@@ -56,8 +59,14 @@
      * Test the available capability for different hardware support level devices.
      */
     public void testHwSupportedLevel() throws Exception {
+        Key<StreamConfigurationMap> key =
+                CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP;
+        final float SIZE_ERROR_MARGIN = 0.03f;
         for (String id : mCameraIds) {
             initStaticMetadata(id);
+            StreamConfigurationMap configs = mStaticInfo.getValueFromKeyNonNull(key);
+            Rect activeRect = mStaticInfo.getActiveArraySizeChecked();
+            Size sensorSize = new Size(activeRect.width(), activeRect.height());
             List<Integer> availableCaps = mStaticInfo.getAvailableCapabilitiesChecked();
 
             mCollector.expectTrue("All device must contains BACKWARD_COMPATIBLE capability",
@@ -71,6 +80,13 @@
                         availableCaps.contains(
                                 REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING));
 
+                // Max yuv resolution must be very close to  sensor resolution
+                Size[] yuvSizes = configs.getOutputSizes(ImageFormat.YUV_420_888);
+                Size maxYuvSize = CameraTestUtils.getMaxSize(yuvSizes);
+                mCollector.expectSizesAreSimilar(
+                        "Active array size and max YUV size should be similar",
+                        sensorSize, maxYuvSize, SIZE_ERROR_MARGIN);
+
                 // Max resolution fps must be >= 20.
                 mCollector.expectTrue("Full device must support at least 20fps for max resolution",
                         getFpsForMaxSize(id) >= MIN_FPS_FOR_FULL_DEVICE);
@@ -80,6 +96,13 @@
                         mStaticInfo.isPerFrameControlSupported());
             }
 
+            // Max jpeg resolution must be very close to  sensor resolution
+            Size[] jpegSizes = configs.getOutputSizes(ImageFormat.JPEG);
+            Size maxJpegSize = CameraTestUtils.getMaxSize(jpegSizes);
+            mCollector.expectSizesAreSimilar(
+                    "Active array size and max JPEG size should be similar",
+                    sensorSize, maxJpegSize, SIZE_ERROR_MARGIN);
+
             // TODO: test all the keys mandatory for all capability devices.
         }
     }
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/StillCaptureTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/StillCaptureTest.java
index f18a1cf..37eff10 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/StillCaptureTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/StillCaptureTest.java
@@ -291,6 +291,12 @@
             try {
                 Log.i(TAG, "Testing AE compensation for Camera " + id);
                 openDevice(id);
+
+                if (mStaticInfo.isHardwareLevelLegacy()) {
+                    Log.i(TAG, "Skipping test on legacy devices");
+                    continue;
+                }
+
                 aeCompensationTestByCamera();
             } finally {
                 closeDevice();
@@ -434,6 +440,10 @@
         waitForNumResults(resultListener, NUM_FRAMES_WAITED);
 
         stopPreview();
+
+        // Free image resources
+        image.close();
+        closeImageReader();
         return;
     }
 
@@ -603,6 +613,9 @@
         Image image = imageListener.getImage(CAPTURE_IMAGE_TIMEOUT_MS);
         validateJpegCapture(image, maxStillSz);
 
+        // Free image resources
+        image.close();
+
         stopPreview();
     }
 
@@ -648,6 +661,10 @@
                 mSession.capture(stillRequest.build(), resultListener, mHandler);
                 Image image = imageListener.getImage(CAPTURE_IMAGE_TIMEOUT_MS);
                 validateJpegCapture(image, stillSz);
+
+                // Free image resources
+                image.close();
+
                 // stopPreview must be called here to make sure next time a preview stream
                 // is created with new size.
                 stopPreview();
@@ -698,6 +715,9 @@
             dumpFile(rawFileName, rawBuffer);
         }
 
+        // Free image resources
+        image.close();
+
         stopPreview();
     }
 
@@ -1015,6 +1035,9 @@
             if (!mStaticInfo.isHardwareLevelLegacy()) {
                 jpegTestExifExtraTags(exif, maxStillSz, stillResult);
             }
+
+            // Free image resources
+            image.close();
         }
     }
 
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/CameraErrorCollector.java b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/CameraErrorCollector.java
index 7cf4089..f4859e5 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/CameraErrorCollector.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/CameraErrorCollector.java
@@ -520,6 +520,37 @@
     }
 
     /**
+     * Check that two sizes are similar enough by ensuring that their width and height
+     * are within {@code errorPercent} of each other.
+     *
+     * <p>Only the first error is collected, to avoid spamming several error messages when
+     * the rectangle is hugely dissimilar.</p>
+     *
+     * @param msg Message to be logged
+     * @param expected The reference 'expected' value to be used to check against
+     * @param actual The actual value that was received
+     * @param errorPercent Within how many percent the components should be
+     *
+     * @return {@code true} if all expects passed, {@code false} otherwise
+     */
+    public boolean expectSizesAreSimilar(String msg, Size expected, Size actual,
+            float errorPercent) {
+        String formattedMsg = String.format("%s: rects are not similar enough; expected (%s), " +
+                "actual (%s), error percent (%s), reason: ",
+                msg, expected, actual, errorPercent);
+
+        if (!expectSimilarValues(
+                formattedMsg, "too wide", "too narrow", actual.getWidth(), expected.getWidth(),
+                errorPercent)) return false;
+
+        if (!expectSimilarValues(
+                formattedMsg, "too tall", "too short", actual.getHeight(), expected.getHeight(),
+                errorPercent)) return false;
+
+        return true;
+    }
+
+    /**
      * Check that the rectangle is centered within a certain tolerance of {@code errorPercent},
      * with respect to the {@code bounds} bounding rectangle.
      *
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/CameraMetadataGetter.java b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/CameraMetadataGetter.java
new file mode 100755
index 0000000..db75cdd
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/CameraMetadataGetter.java
@@ -0,0 +1,690 @@
+/*
+ * 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.hardware.camera2.cts.helpers;
+
+import static com.android.ex.camera2.blocking.BlockingStateCallback.*;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.BlackLevelPattern;
+import android.hardware.camera2.params.ColorSpaceTransform;
+import android.hardware.camera2.params.Face;
+import android.hardware.camera2.params.LensShadingMap;
+import android.hardware.camera2.params.MeteringRectangle;
+import android.hardware.camera2.params.RggbChannelVector;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.hardware.camera2.params.TonemapCurve;
+import android.location.Location;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Rational;
+import android.util.Size;
+import android.util.SizeF;
+import android.util.Range;
+
+import com.android.ex.camera2.blocking.BlockingCameraManager;
+import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException;
+import com.android.ex.camera2.blocking.BlockingStateCallback;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+
+/**
+ * Utility class to dump the camera metadata.
+ */
+public final class CameraMetadataGetter implements AutoCloseable {
+    private static final String TAG = CameraMetadataGetter.class.getSimpleName();
+    private static final int CAMERA_CLOSE_TIMEOUT_MS = 5000;
+    public static final int[] TEMPLATE_IDS = {
+        CameraDevice.TEMPLATE_PREVIEW,
+        CameraDevice.TEMPLATE_STILL_CAPTURE,
+        CameraDevice.TEMPLATE_RECORD,
+        CameraDevice.TEMPLATE_VIDEO_SNAPSHOT,
+        CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG,
+        CameraDevice.TEMPLATE_MANUAL,
+    };
+    private CameraManager mCameraManager;
+    private BlockingStateCallback mCameraListener;
+    private HandlerThread mHandlerThread;
+    private Handler mHandler;
+
+    private static class MetadataEntry {
+        public MetadataEntry(String k, Object v) {
+            key = k;
+            value = v;
+        }
+
+        public String key;
+        public Object value;
+    }
+
+    public CameraMetadataGetter(CameraManager cameraManager) {
+        if (cameraManager == null) {
+            throw new IllegalArgumentException("can not create an CameraMetadataGetter object"
+                    + " with null CameraManager");
+        }
+
+        mCameraManager = cameraManager;
+
+        mCameraListener = new BlockingStateCallback();
+        mHandlerThread = new HandlerThread(TAG);
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+    }
+
+    public String getCameraInfo() {
+        StringBuffer cameraInfo = new StringBuffer("{\"CameraStaticMetadata\":{");
+        CameraCharacteristics staticMetadata;
+        String[] cameraIds;
+        try {
+            cameraIds = mCameraManager.getCameraIdList();
+        } catch (CameraAccessException e) {
+            Log.e(TAG, "Unable to get camera ids, skip this info, error: " + e.getMessage());
+            return "";
+        }
+        for (String id : cameraIds) {
+            String value = null;
+            try {
+                staticMetadata = mCameraManager.getCameraCharacteristics(id);
+                value = serialize(staticMetadata).toString();
+            } catch (CameraAccessException e) {
+                Log.e(TAG,
+                        "Unable to get camera camera static info, skip this camera, error: "
+                                + e.getMessage());
+            }
+            cameraInfo.append("\"camera" + id + "\":"); // Key
+            cameraInfo.append(value); // Value
+            // If not last, print "," // Separator
+            if (!id.equals(cameraIds[cameraIds.length - 1])) {
+                cameraInfo.append(",");
+            }
+        }
+        cameraInfo.append("}}");
+
+        return cameraInfo.toString();
+    }
+
+    public JSONObject getCameraInfo(String cameraId) {
+        JSONObject staticMetadata = null;
+        try {
+            staticMetadata = serialize(mCameraManager.getCameraCharacteristics(cameraId));
+        } catch (CameraAccessException e) {
+            Log.e(TAG,
+                    "Unable to get camera camera static info, skip this camera, error: "
+                            + e.getMessage());
+        }
+        return staticMetadata;
+    }
+
+    public JSONObject[] getCaptureRequestTemplates(String cameraId) {
+        JSONObject[] templates = new JSONObject[TEMPLATE_IDS.length];
+        CameraDevice camera = null;
+        try {
+            camera = (new BlockingCameraManager(mCameraManager)).openCamera(cameraId,
+                            mCameraListener, mHandler);
+            for (int i = 0; i < TEMPLATE_IDS.length; i++) {
+                CaptureRequest.Builder request;
+                try {
+                    request = camera.createCaptureRequest(TEMPLATE_IDS[i]);
+                    templates[i] = serialize(request.build());
+                } catch (Exception e) {
+                    Log.e(TAG, "Unable to create template " + TEMPLATE_IDS[i]
+                                    + " because of error " + e.getMessage());
+                    templates[i] = null;
+                }
+            }
+            return templates;
+        } catch (CameraAccessException | BlockingOpenException e) {
+            Log.e(TAG, "Unable to open camera " + cameraId + " because of error "
+                            + e.getMessage());
+            return new JSONObject[0];
+        } finally {
+            if (camera != null) {
+                camera.close();
+            }
+        }
+    }
+
+    public String getCaptureRequestTemplates() {
+        StringBuffer templates = new StringBuffer("{\"CameraRequestTemplates\":{");
+        String[] cameraIds;
+        try {
+            cameraIds = mCameraManager.getCameraIdList();
+        } catch (CameraAccessException e) {
+            Log.e(TAG, "Unable to get camera ids, skip this info, error: " + e.getMessage());
+            return "";
+        }
+        CameraDevice camera = null;
+        for (String id : cameraIds) {
+            try {
+                try {
+                    camera = (new BlockingCameraManager(mCameraManager)).openCamera(id,
+                                    mCameraListener, mHandler);
+                } catch (CameraAccessException | BlockingOpenException e) {
+                    Log.e(TAG, "Unable to open camera " + id + " because of error "
+                                    + e.getMessage());
+                    continue;
+                }
+
+                for (int i = 0; i < TEMPLATE_IDS.length; i++) {
+                    String value = null;
+                    CaptureRequest.Builder request;
+                    try {
+                        request = camera.createCaptureRequest(TEMPLATE_IDS[i]);
+                        value = serialize(request.build()).toString();
+                    } catch (Exception e) {
+                        Log.e(TAG, "Unable to create template " + TEMPLATE_IDS[i]
+                                        + " because of error " + e.getMessage());
+                    }
+                    templates.append("\"Camera" + id + "CaptureTemplate" +
+                                    TEMPLATE_IDS[i] + "\":");
+                    templates.append(value);
+                    if (!id.equals(cameraIds[cameraIds.length - 1]) ||
+                                    i < (TEMPLATE_IDS.length - 1)) {
+                        templates.append(",");
+                    }
+                }
+            } finally {
+                if (camera != null) {
+                    camera.close();
+                    mCameraListener.waitForState(STATE_CLOSED, CAMERA_CLOSE_TIMEOUT_MS);
+                }
+            }
+        }
+
+        templates.append("}}");
+        return templates.toString();
+    }
+
+    /*
+     * Cleanup the resources.
+     */
+    @Override
+    public void close() throws Exception {
+        mHandlerThread.quitSafely();
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            close();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializeRational(Rational rat) throws org.json.JSONException {
+        JSONObject ratObj = new JSONObject();
+        ratObj.put("numerator", rat.getNumerator());
+        ratObj.put("denominator", rat.getDenominator());
+        return ratObj;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializeSize(Size size) throws org.json.JSONException {
+        JSONObject sizeObj = new JSONObject();
+        sizeObj.put("width", size.getWidth());
+        sizeObj.put("height", size.getHeight());
+        return sizeObj;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializeSizeF(SizeF size) throws org.json.JSONException {
+        JSONObject sizeObj = new JSONObject();
+        sizeObj.put("width", size.getWidth());
+        sizeObj.put("height", size.getHeight());
+        return sizeObj;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializeRect(Rect rect) throws org.json.JSONException {
+        JSONObject rectObj = new JSONObject();
+        rectObj.put("left", rect.left);
+        rectObj.put("right", rect.right);
+        rectObj.put("top", rect.top);
+        rectObj.put("bottom", rect.bottom);
+        return rectObj;
+    }
+
+    private static Object serializePoint(Point point) throws org.json.JSONException {
+        JSONObject pointObj = new JSONObject();
+        pointObj.put("x", point.x);
+        pointObj.put("y", point.y);
+        return pointObj;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializeFace(Face face)
+                    throws org.json.JSONException {
+        JSONObject faceObj = new JSONObject();
+        faceObj.put("bounds", serializeRect(face.getBounds()));
+        faceObj.put("score", face.getScore());
+        faceObj.put("id", face.getId());
+        faceObj.put("leftEye", serializePoint(face.getLeftEyePosition()));
+        faceObj.put("rightEye", serializePoint(face.getRightEyePosition()));
+        faceObj.put("mouth", serializePoint(face.getMouthPosition()));
+        return faceObj;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializeStreamConfigurationMap(
+                    StreamConfigurationMap map)
+                    throws org.json.JSONException {
+        // TODO: Serialize the rest of the StreamConfigurationMap fields.
+        JSONObject mapObj = new JSONObject();
+        JSONArray cfgArray = new JSONArray();
+        int fmts[] = map.getOutputFormats();
+        if (fmts != null) {
+            for (int fi = 0; fi < Array.getLength(fmts); fi++) {
+                Size sizes[] = map.getOutputSizes(fmts[fi]);
+                if (sizes != null) {
+                    for (int si = 0; si < Array.getLength(sizes); si++) {
+                        JSONObject obj = new JSONObject();
+                        obj.put("format", fmts[fi]);
+                        obj.put("width", sizes[si].getWidth());
+                        obj.put("height", sizes[si].getHeight());
+                        obj.put("input", false);
+                        obj.put("minFrameDuration",
+                                        map.getOutputMinFrameDuration(fmts[fi], sizes[si]));
+                        cfgArray.put(obj);
+                    }
+                }
+            }
+        }
+        mapObj.put("availableStreamConfigurations", cfgArray);
+        return mapObj;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializeMeteringRectangle(MeteringRectangle rect)
+                    throws org.json.JSONException {
+        JSONObject rectObj = new JSONObject();
+        rectObj.put("x", rect.getX());
+        rectObj.put("y", rect.getY());
+        rectObj.put("width", rect.getWidth());
+        rectObj.put("height", rect.getHeight());
+        rectObj.put("weight", rect.getMeteringWeight());
+        return rectObj;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializePair(Pair pair)
+                    throws org.json.JSONException {
+        JSONArray pairObj = new JSONArray();
+        pairObj.put(pair.first);
+        pairObj.put(pair.second);
+        return pairObj;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializeRange(Range range)
+                    throws org.json.JSONException {
+        JSONArray rangeObj = new JSONArray();
+        rangeObj.put(range.getLower());
+        rangeObj.put(range.getUpper());
+        return rangeObj;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializeColorSpaceTransform(ColorSpaceTransform xform)
+                    throws org.json.JSONException {
+        JSONArray xformObj = new JSONArray();
+        for (int row = 0; row < 3; row++) {
+            for (int col = 0; col < 3; col++) {
+                xformObj.put(serializeRational(xform.getElement(col, row)));
+            }
+        }
+        return xformObj;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializeTonemapCurve(TonemapCurve curve)
+                    throws org.json.JSONException {
+        JSONObject curveObj = new JSONObject();
+        String names[] = {
+                        "red", "green", "blue" };
+        for (int ch = 0; ch < 3; ch++) {
+            JSONArray curveArr = new JSONArray();
+            int len = curve.getPointCount(ch);
+            for (int i = 0; i < len; i++) {
+                curveArr.put(curve.getPoint(ch, i).x);
+                curveArr.put(curve.getPoint(ch, i).y);
+            }
+            curveObj.put(names[ch], curveArr);
+        }
+        return curveObj;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializeRggbChannelVector(RggbChannelVector vec)
+                    throws org.json.JSONException {
+        JSONArray vecObj = new JSONArray();
+        vecObj.put(vec.getRed());
+        vecObj.put(vec.getGreenEven());
+        vecObj.put(vec.getGreenOdd());
+        vecObj.put(vec.getBlue());
+        return vecObj;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializeBlackLevelPattern(BlackLevelPattern pat)
+                    throws org.json.JSONException {
+        int patVals[] = new int[4];
+        pat.copyTo(patVals, 0);
+        JSONArray patObj = new JSONArray();
+        patObj.put(patVals[0]);
+        patObj.put(patVals[1]);
+        patObj.put(patVals[2]);
+        patObj.put(patVals[3]);
+        return patObj;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializeLocation(Location loc)
+                    throws org.json.JSONException {
+        return loc.toString();
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializeLensShadingMap(LensShadingMap map)
+            throws org.json.JSONException {
+        JSONArray mapObj = new JSONArray();
+        for (int row = 0; row < map.getRowCount(); row++) {
+            for (int col = 0; col < map.getColumnCount(); col++) {
+                for (int ch = 0; ch < 4; ch++) {
+                    mapObj.put(map.getGainFactor(ch, col, row));
+                }
+            }
+        }
+        return mapObj;
+    }
+
+    private static String getKeyName(Object keyObj) {
+        if (keyObj.getClass() == CaptureResult.Key.class
+                || keyObj.getClass() == TotalCaptureResult.class) {
+            return ((CaptureResult.Key) keyObj).getName();
+        } else if (keyObj.getClass() == CaptureRequest.Key.class) {
+            return ((CaptureRequest.Key) keyObj).getName();
+        } else if (keyObj.getClass() == CameraCharacteristics.Key.class) {
+            return ((CameraCharacteristics.Key) keyObj).getName();
+        }
+
+        throw new IllegalArgumentException("Invalid key object");
+    }
+
+    private static Object getKeyValue(CameraMetadata md, Object keyObj) {
+        if (md.getClass() == CaptureResult.class || md.getClass() == TotalCaptureResult.class) {
+            return ((CaptureResult) md).get((CaptureResult.Key) keyObj);
+        } else if (md.getClass() == CaptureRequest.class) {
+            return ((CaptureRequest) md).get((CaptureRequest.Key) keyObj);
+        } else if (md.getClass() == CameraCharacteristics.class) {
+            return ((CameraCharacteristics) md).get((CameraCharacteristics.Key) keyObj);
+        }
+
+        throw new IllegalArgumentException("Invalid key object");
+    }
+
+    @SuppressWarnings("unchecked")
+    private static MetadataEntry serializeEntry(Type keyType, Object keyObj, CameraMetadata md) {
+        String keyName = getKeyName(keyObj);
+
+        try {
+            Object keyValue = getKeyValue(md, keyObj);
+            if (keyValue == null) {
+                return new MetadataEntry(keyName, JSONObject.NULL);
+            } else if (keyType == Float.class) {
+                // The JSON serializer doesn't handle floating point NaN or Inf.
+                if (((Float) keyValue).isInfinite() || ((Float) keyValue).isNaN()) {
+                    Log.w(TAG, "Inf/NaN floating point value serialized: " + keyName);
+                    return null;
+                }
+                return new MetadataEntry(keyName, keyValue);
+            } else if (keyType == Integer.class || keyType == Long.class || keyType == Byte.class ||
+                    keyType == Boolean.class || keyType == String.class) {
+                return new MetadataEntry(keyName, keyValue);
+            } else if (keyType == Rational.class) {
+                return new MetadataEntry(keyName, serializeRational((Rational) keyValue));
+            } else if (keyType == Size.class) {
+                return new MetadataEntry(keyName, serializeSize((Size) keyValue));
+            } else if (keyType == SizeF.class) {
+                return new MetadataEntry(keyName, serializeSizeF((SizeF) keyValue));
+            } else if (keyType == Rect.class) {
+                return new MetadataEntry(keyName, serializeRect((Rect) keyValue));
+            } else if (keyType == Face.class) {
+                return new MetadataEntry(keyName, serializeFace((Face) keyValue));
+            } else if (keyType == StreamConfigurationMap.class) {
+                return new MetadataEntry(keyName,
+                        serializeStreamConfigurationMap((StreamConfigurationMap) keyValue));
+            } else if (keyType instanceof ParameterizedType &&
+                    ((ParameterizedType) keyType).getRawType() == Range.class) {
+                return new MetadataEntry(keyName, serializeRange((Range) keyValue));
+            } else if (keyType == ColorSpaceTransform.class) {
+                return new MetadataEntry(keyName,
+                        serializeColorSpaceTransform((ColorSpaceTransform) keyValue));
+            } else if (keyType == MeteringRectangle.class) {
+                return new MetadataEntry(keyName,
+                        serializeMeteringRectangle((MeteringRectangle) keyValue));
+            } else if (keyType == Location.class) {
+                return new MetadataEntry(keyName,
+                        serializeLocation((Location) keyValue));
+            } else if (keyType == RggbChannelVector.class) {
+                return new MetadataEntry(keyName,
+                        serializeRggbChannelVector((RggbChannelVector) keyValue));
+            } else if (keyType == BlackLevelPattern.class) {
+                return new MetadataEntry(keyName,
+                        serializeBlackLevelPattern((BlackLevelPattern) keyValue));
+            } else if (keyType == TonemapCurve.class) {
+                return new MetadataEntry(keyName,
+                        serializeTonemapCurve((TonemapCurve) keyValue));
+            } else if (keyType == Point.class) {
+                return new MetadataEntry(keyName,
+                        serializePoint((Point) keyValue));
+            } else if (keyType == LensShadingMap.class) {
+                return new MetadataEntry(keyName,
+                        serializeLensShadingMap((LensShadingMap) keyValue));
+            } else {
+                Log.w(TAG, String.format("Serializing unsupported key type: " + keyType));
+                return null;
+            }
+        } catch (org.json.JSONException e) {
+            throw new IllegalStateException("JSON error for key: " + keyName + ": ", e);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private static MetadataEntry serializeArrayEntry(Type keyType, Object keyObj,
+            CameraMetadata md) {
+        String keyName = getKeyName(keyObj);
+        try {
+            Object keyValue = getKeyValue(md, keyObj);
+            if (keyValue == null) {
+                return new MetadataEntry(keyName, JSONObject.NULL);
+            }
+            int arrayLen = Array.getLength(keyValue);
+            Type elmtType = ((GenericArrayType) keyType).getGenericComponentType();
+            if (elmtType == int.class || elmtType == float.class || elmtType == byte.class ||
+                    elmtType == long.class || elmtType == double.class
+                    || elmtType == boolean.class) {
+                return new MetadataEntry(keyName, new JSONArray(keyValue));
+            } else if (elmtType == Rational.class) {
+                JSONArray jsonArray = new JSONArray();
+                for (int i = 0; i < arrayLen; i++) {
+                    jsonArray.put(serializeRational((Rational) Array.get(keyValue, i)));
+                }
+                return new MetadataEntry(keyName, jsonArray);
+            } else if (elmtType == Size.class) {
+                JSONArray jsonArray = new JSONArray();
+                for (int i = 0; i < arrayLen; i++) {
+                    jsonArray.put(serializeSize((Size) Array.get(keyValue, i)));
+                }
+                return new MetadataEntry(keyName, jsonArray);
+            } else if (elmtType == Rect.class) {
+                JSONArray jsonArray = new JSONArray();
+                for (int i = 0; i < arrayLen; i++) {
+                    jsonArray.put(serializeRect((Rect) Array.get(keyValue, i)));
+                }
+                return new MetadataEntry(keyName, jsonArray);
+            } else if (elmtType == Face.class) {
+                JSONArray jsonArray = new JSONArray();
+                for (int i = 0; i < arrayLen; i++) {
+                    jsonArray.put(serializeFace((Face) Array.get(keyValue, i)));
+                }
+                return new MetadataEntry(keyName, jsonArray);
+            } else if (elmtType == StreamConfigurationMap.class) {
+                JSONArray jsonArray = new JSONArray();
+                for (int i = 0; i < arrayLen; i++) {
+                    jsonArray.put(serializeStreamConfigurationMap(
+                            (StreamConfigurationMap) Array.get(keyValue, i)));
+                }
+                return new MetadataEntry(keyName, jsonArray);
+            } else if (elmtType instanceof ParameterizedType &&
+                    ((ParameterizedType) elmtType).getRawType() == Range.class) {
+                JSONArray jsonArray = new JSONArray();
+                for (int i = 0; i < arrayLen; i++) {
+                    jsonArray.put(serializeRange((Range) Array.get(keyValue, i)));
+                }
+                return new MetadataEntry(keyName, jsonArray);
+            } else if (elmtType instanceof ParameterizedType &&
+                    ((ParameterizedType) elmtType).getRawType() == Pair.class) {
+                JSONArray jsonArray = new JSONArray();
+                for (int i = 0; i < arrayLen; i++) {
+                    jsonArray.put(serializePair((Pair) Array.get(keyValue, i)));
+                }
+                return new MetadataEntry(keyName, jsonArray);
+            } else if (elmtType == MeteringRectangle.class) {
+                JSONArray jsonArray = new JSONArray();
+                for (int i = 0; i < arrayLen; i++) {
+                    jsonArray.put(serializeMeteringRectangle(
+                            (MeteringRectangle) Array.get(keyValue, i)));
+                }
+                return new MetadataEntry(keyName, jsonArray);
+            } else if (elmtType == Location.class) {
+                JSONArray jsonArray = new JSONArray();
+                for (int i = 0; i < arrayLen; i++) {
+                    jsonArray.put(serializeLocation((Location) Array.get(keyValue, i)));
+                }
+                return new MetadataEntry(keyName, jsonArray);
+            } else if (elmtType == RggbChannelVector.class) {
+                JSONArray jsonArray = new JSONArray();
+                for (int i = 0; i < arrayLen; i++) {
+                    jsonArray.put(serializeRggbChannelVector(
+                            (RggbChannelVector) Array.get(keyValue, i)));
+                }
+                return new MetadataEntry(keyName, jsonArray);
+            } else if (elmtType == BlackLevelPattern.class) {
+                JSONArray jsonArray = new JSONArray();
+                for (int i = 0; i < arrayLen; i++) {
+                    jsonArray.put(serializeBlackLevelPattern(
+                            (BlackLevelPattern) Array.get(keyValue, i)));
+                }
+                return new MetadataEntry(keyName, jsonArray);
+            } else if (elmtType == Point.class) {
+                JSONArray jsonArray = new JSONArray();
+                for (int i = 0; i < arrayLen; i++) {
+                    jsonArray.put(serializePoint((Point) Array.get(keyValue, i)));
+                }
+                return new MetadataEntry(keyName, jsonArray);
+            } else {
+                Log.w(TAG, String.format("Serializing unsupported array type: " + elmtType));
+                return null;
+            }
+        } catch (org.json.JSONException e) {
+            throw new IllegalStateException("JSON error for key: " + keyName + ": ", e);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private static JSONObject serialize(CameraMetadata md) {
+        JSONObject jsonObj = new JSONObject();
+        Field[] allFields = md.getClass().getDeclaredFields();
+        if (md.getClass() == TotalCaptureResult.class) {
+            allFields = CaptureResult.class.getDeclaredFields();
+        }
+        for (Field field : allFields) {
+            if (Modifier.isPublic(field.getModifiers()) &&
+                    Modifier.isStatic(field.getModifiers()) &&
+                            (field.getType() == CaptureRequest.Key.class
+                            || field.getType() == CaptureResult.Key.class
+                            || field.getType() == TotalCaptureResult.Key.class
+                            || field.getType() == CameraCharacteristics.Key.class)
+                    &&
+                    field.getGenericType() instanceof ParameterizedType) {
+                ParameterizedType paramType = (ParameterizedType) field.getGenericType();
+                Type[] argTypes = paramType.getActualTypeArguments();
+                if (argTypes.length > 0) {
+                    try {
+                        Type keyType = argTypes[0];
+                        Object keyObj = field.get(md);
+                        MetadataEntry entry;
+                        if (keyType instanceof GenericArrayType) {
+                            entry = serializeArrayEntry(keyType, keyObj, md);
+                        } else {
+                            entry = serializeEntry(keyType, keyObj, md);
+                        }
+
+                        // TODO: Figure this weird case out.
+                        // There is a weird case where the entry is non-null but
+                        // the toString
+                        // of the entry is null, and if this happens, the
+                        // null-ness spreads like
+                        // a virus and makes the whole JSON object null from the
+                        // top level down.
+                        // Not sure if it's a bug in the library or I'm just not
+                        // using it right.
+                        // Workaround by checking for this case explicitly and
+                        // not adding the
+                        // value to the jsonObj when it is detected.
+                        if (entry != null && entry.key != null && entry.value != null
+                                && entry.value.toString() == null) {
+                            Log.w(TAG, "Error encountered serializing value for key: "
+                                    + entry.key);
+                        } else if (entry != null) {
+                            jsonObj.put(entry.key, entry.value);
+                        } else {
+                            // Ignore.
+                        }
+                    } catch (IllegalAccessException e) {
+                        throw new IllegalStateException(
+                                "Access error for field: " + field + ": ", e);
+                    } catch (org.json.JSONException e) {
+                        throw new IllegalStateException(
+                                "JSON error for field: " + field + ": ", e);
+                    }
+                }
+            }
+        }
+        return jsonObj;
+    }
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/LowRamDeviceTest.java b/tests/tests/hardware/src/android/hardware/cts/LowRamDeviceTest.java
index 7c711a2..0e09e8c 100644
--- a/tests/tests/hardware/src/android/hardware/cts/LowRamDeviceTest.java
+++ b/tests/tests/hardware/src/android/hardware/cts/LowRamDeviceTest.java
@@ -16,9 +16,28 @@
 
 package android.hardware.cts;
 
+import static android.content.res.Configuration.SCREENLAYOUT_SIZE_LARGE;
+import static android.content.res.Configuration.SCREENLAYOUT_SIZE_NORMAL;
+import static android.content.res.Configuration.SCREENLAYOUT_SIZE_SMALL;
+import static android.content.res.Configuration.SCREENLAYOUT_SIZE_XLARGE;
+
+import static android.util.DisplayMetrics.DENSITY_400;
+import static android.util.DisplayMetrics.DENSITY_560;
+import static android.util.DisplayMetrics.DENSITY_HIGH;
+import static android.util.DisplayMetrics.DENSITY_LOW;
+import static android.util.DisplayMetrics.DENSITY_MEDIUM;
+import static android.util.DisplayMetrics.DENSITY_TV;
+import static android.util.DisplayMetrics.DENSITY_XHIGH;
+
 import android.app.ActivityManager;
 import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.os.Build;
 import android.test.AndroidTestCase;
+import android.util.DisplayMetrics;
+import android.view.WindowManager;
+import android.util.Log;
 
 import java.io.FileInputStream;
 import java.io.IOException;
@@ -31,45 +50,142 @@
  */
 public class LowRamDeviceTest extends AndroidTestCase {
 
-    private static final int LOW_RAM_DEVICE_MEMORY_THRESHOLD_KB = 512 * 1024;
+    private static final long ONE_MEGABYTE = 1048576L;
+    private static final String TAG = "LowRamDeviceTest";
 
-    public void testLowRamProductProperty() throws Exception {
-        ActivityManager am =
+    private PackageManager mPackageManager;
+    private ActivityManager mActivityManager;
+    private DisplayMetrics mDisplayMetrics;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mPackageManager = getContext().getPackageManager();
+        mActivityManager =
                 (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE);
 
-        if (totalAvailableSystemMemory() <= LOW_RAM_DEVICE_MEMORY_THRESHOLD_KB) {
-            assertTrue("Device must specify low RAM property: ro.config.low_ram=true",
-                    am.isLowRamDevice());
+        mDisplayMetrics = new DisplayMetrics();
+        WindowManager windowManager =
+                (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
+        windowManager.getDefaultDisplay().getMetrics(mDisplayMetrics);
+    }
+
+    /**
+     * Test the devices reported memory to ensure it meets the minimum values described
+     * in CDD 7.6.1.
+     */
+    public void testMinimumMemory() {
+        int density = mDisplayMetrics.densityDpi;
+        Boolean supports64Bit = supportsSixtyFourBit();
+        int screenSize = getScreenSize();
+        Boolean lowRamDevice = mActivityManager.isLowRamDevice();
+        Boolean watch = mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH);
+
+        Log.i(TAG, String.format("density=%d, supports64Bit=%s, screenSize=%d, watch=%s",
+                density, supports64Bit, screenSize, watch));
+
+        if (watch) {
+            assertFalse("Device is not expected to be 64-bit", supports64Bit);
+            assertMinMemoryMb(416);
+        } else if (lessThanDpi(density, DENSITY_HIGH, screenSize,
+                SCREENLAYOUT_SIZE_NORMAL, SCREENLAYOUT_SIZE_SMALL) ||
+                lessThanDpi(density, DENSITY_MEDIUM, screenSize, SCREENLAYOUT_SIZE_LARGE) ||
+                lessThanDpi(density, DENSITY_LOW, screenSize, SCREENLAYOUT_SIZE_XLARGE)) {
+
+            assertFalse("Device is not expected to be 64-bit", supports64Bit);
+            assertMinMemoryMb(424);
+        } else if (greaterThanDpi(density, DENSITY_XHIGH, screenSize,
+                SCREENLAYOUT_SIZE_NORMAL, SCREENLAYOUT_SIZE_SMALL) ||
+                greaterThanDpi(density, DENSITY_TV, screenSize, SCREENLAYOUT_SIZE_LARGE) ||
+                greaterThanDpi(density, DENSITY_MEDIUM, screenSize, SCREENLAYOUT_SIZE_XLARGE)) {
+
+            if (supports64Bit) {
+                assertMinMemoryMb(832);
+            } else {
+                assertMinMemoryMb(512);
+            }
+        } else if (greaterThanDpi(density, DENSITY_400, screenSize,
+                SCREENLAYOUT_SIZE_NORMAL, SCREENLAYOUT_SIZE_SMALL) ||
+                greaterThanDpi(density, DENSITY_XHIGH, screenSize, SCREENLAYOUT_SIZE_LARGE) ||
+                greaterThanDpi(density, DENSITY_TV, screenSize, SCREENLAYOUT_SIZE_XLARGE)) {
+
+            if (supports64Bit) {
+                assertMinMemoryMb(1280);
+            } else {
+                assertMinMemoryMb(896);
+            }
+        } else if (greaterThanDpi(density, DENSITY_560, screenSize,
+                SCREENLAYOUT_SIZE_NORMAL, SCREENLAYOUT_SIZE_SMALL) ||
+                greaterThanDpi(density, DENSITY_400, screenSize, SCREENLAYOUT_SIZE_LARGE) ||
+                greaterThanDpi(density, DENSITY_XHIGH, screenSize, SCREENLAYOUT_SIZE_XLARGE)) {
+
+            if (supports64Bit) {
+                assertMinMemoryMb(1824);
+            } else {
+                assertMinMemoryMb(1344);
+            }
         }
     }
 
     /**
-     * Returns the total amount of memory in kilobytes available to the system.
+     * @return the total memory accessible by the kernel as defined by
+     * {@code ActivityManager.MemoryInfo}.
      */
-    private int totalAvailableSystemMemory() throws IOException {
-        final String property = "MemTotal";
-        InputStream is = new FileInputStream("/proc/meminfo");
-        try {
-            Scanner scanner = new Scanner(is);
-            while (scanner.hasNextLine()) {
-                String line = scanner.nextLine();
-                if (line.startsWith(property)) {
-                    StringTokenizer tokenizer = new StringTokenizer(line);
-                    if (tokenizer.countTokens() != 3) {
-                        throw new IOException("Malformed " + property + " line");
-                    }
-
-                    // Skips over "MemTotal:"
-                    tokenizer.nextToken();
-
-                    return Integer.parseInt(tokenizer.nextToken());
-                }
-            }
-            throw new IOException(property + " could not be found");
-
-        } finally {
-            is.close();
-        }
+    private long getTotalMemory() {
+        ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
+        mActivityManager.getMemoryInfo(memoryInfo);
+        return memoryInfo.totalMem;
     }
 
+    /** @return the screen size as defined in {@Configuration}. */
+    private int getScreenSize() {
+        Configuration config = getContext().getResources().getConfiguration();
+        return config.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK;
+    }
+
+    /** @return true iff this device supports 64 bit ABIs */
+    private static boolean supportsSixtyFourBit() {
+        return Build.SUPPORTED_64_BIT_ABIS.length > 0;
+    }
+
+    /** Asserts that the given values conform to the specs in CDD 7.6.1 */
+    private void assertMinMemoryMb(long minMb) {
+
+        long totalMemoryMb = getTotalMemory() / ONE_MEGABYTE;
+        boolean lowRam = totalMemoryMb <= minMb * 1.5;
+        boolean lowRamDevice = mActivityManager.isLowRamDevice();
+
+        Log.i(TAG, String.format("minMb=%,d", minMb));
+        Log.i(TAG, String.format("totalMemoryMb=%,d", totalMemoryMb));
+        Log.i(TAG, "lowRam=" + lowRam);
+        Log.i(TAG, "lowRamDevice=" + lowRamDevice);
+
+        assertTrue(String.format("Does not meet minimum memory requirements (CDD 7.6.1)."
+                + "Found = %d, Minimum = %d", totalMemoryMb, minMb), totalMemoryMb >= minMb);
+
+        assertTrue("Device must specify low RAM property: ro.config.low_ram=true",
+                !lowRam || (lowRam && lowRamDevice));
+    }
+
+    private static boolean lessThanDpi(int actualDensityDpi, int expectedDensityDpi,
+            int actualScreenSize, int... expectedScreenSizes) {
+        return actualDensityDpi <= expectedDensityDpi &&
+                contains(expectedScreenSizes, actualScreenSize);
+    }
+
+    private static boolean greaterThanDpi(int actualDensityDpi, int expectedDensityDpi,
+            int actualScreenSize, int... expectedScreenSizes) {
+        return actualDensityDpi >= expectedDensityDpi &&
+                contains(expectedScreenSizes, actualScreenSize);
+    }
+
+    /** @return true iff the {@code array} contains the {@code target} */
+    private static boolean contains(int [] array, int target) {
+        for(int a : array) {
+            if (a == target) {
+                return true;
+            }
+        }
+        return false;
+    }
 }
diff --git a/tests/tests/hardware/src/android/hardware/cts/SensorTest.java b/tests/tests/hardware/src/android/hardware/cts/SensorTest.java
index d8b8e51..e8e0f25 100644
--- a/tests/tests/hardware/src/android/hardware/cts/SensorTest.java
+++ b/tests/tests/hardware/src/android/hardware/cts/SensorTest.java
@@ -33,6 +33,7 @@
 import android.os.SystemClock;
 import android.util.Log;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -502,15 +503,23 @@
     // Call registerListener for multiple sensors at a time and call flush.
     public void testBatchAndFlushWithMutipleSensors() throws Exception {
         final int MAX_SENSORS = 3;
-        int numSensors = mSensorList.size() < MAX_SENSORS ? mSensorList.size() : MAX_SENSORS;
-        if (numSensors == 0) {
+        List<Sensor> sensorsToTest = new ArrayList<Sensor>();
+        for (Sensor sensor : mSensorList) {
+            if (sensor.getReportingMode() == Sensor.REPORTING_MODE_CONTINUOUS) {
+                sensorsToTest.add(sensor);
+                if (sensorsToTest.size()  == MAX_SENSORS) break;
+            }
+        }
+
+        final int numSensorsToTest = sensorsToTest.size();
+        if (numSensorsToTest == 0) {
             return;
         }
         final int numEvents = 500;
         int rateUs = 0; // DELAY_FASTEST
         final int maxBatchReportLatencyUs = 10000000;
-        final CountDownLatch eventsRemaining = new CountDownLatch(numSensors * numEvents);
-        final CountDownLatch flushReceived = new CountDownLatch(numSensors);
+        final CountDownLatch eventsRemaining = new CountDownLatch(numSensorsToTest * numEvents);
+        final CountDownLatch flushReceived = new CountDownLatch(numSensorsToTest);
         SensorEventListener2 listener = new SensorEventListener2() {
             @Override
             public void onSensorChanged(SensorEvent event) {
@@ -530,29 +539,20 @@
         try {
             mWakeLock.acquire();
             StringBuilder registeredSensors = new StringBuilder(30);
-            for (Sensor sensor : mSensorList) {
-                // Skip all non-continuous sensors.
-                if (sensor.getReportingMode() != Sensor.REPORTING_MODE_CONTINUOUS) {
-                    continue;
-                }
+            for (Sensor sensor : sensorsToTest) {
                 rateUs = Math.max(sensor.getMinDelay(), rateUs);
                 boolean result = mSensorManager.registerListener(listener, sensor,
                         SensorManager.SENSOR_DELAY_FASTEST, maxBatchReportLatencyUs);
                 assertTrue("registerListener failed for " + sensor.getName(), result);
                 registeredSensors.append(sensor.getName());
                 registeredSensors.append(" ");
-                if (--numSensors == 0) {
-                    break;
-                }
-            }
-            if (registeredSensors.toString().isEmpty()) {
-                return;
             }
 
             Log.i(TAG, "testBatchAndFlushWithMutipleSensors " + registeredSensors);
             long timeToWaitUs =
-                    numEvents*(long)(rateUs/MIN_SAMPLING_FREQUENCY_MULTIPLIER_TOLERANCE) +
-                    maxBatchReportLatencyUs + TIMEOUT_TOLERANCE_US;
+                numEvents*(long)(rateUs/MIN_SAMPLING_FREQUENCY_MULTIPLIER_TOLERANCE) +
+                maxBatchReportLatencyUs + TIMEOUT_TOLERANCE_US;
+
             long totalTimeWaitedUs = waitToCollectAllEvents(timeToWaitUs, maxBatchReportLatencyUs,
                     eventsRemaining);
             if (eventsRemaining.getCount() > 0) {
@@ -563,10 +563,10 @@
             boolean result = mSensorManager.flush(listener);
             assertTrue("flush failed " + registeredSensors.toString(), result);
             boolean collectedFlushEvent =
-                    flushReceived.await(TIMEOUT_TOLERANCE_US, TimeUnit.MICROSECONDS);
+                flushReceived.await(TIMEOUT_TOLERANCE_US, TimeUnit.MICROSECONDS);
             if (!collectedFlushEvent) {
                 fail("Timed out waiting for flushCompleteEvent from " +
-                      registeredSensors.toString() + " waited for=" + timeToWaitUs/1000 + "ms");
+                        registeredSensors.toString() + " waited for=" + timeToWaitUs/1000 + "ms");
             }
             Log.i(TAG, "testBatchAndFlushWithMutipleSensors PASS'd");
         } finally {
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/TestSensorEventListener.java b/tests/tests/hardware/src/android/hardware/cts/helpers/TestSensorEventListener.java
index 9b3a5e4..6567be2 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/TestSensorEventListener.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/TestSensorEventListener.java
@@ -118,7 +118,7 @@
     @Override
     public void onFlushCompleted(Sensor sensor) {
         CountDownLatch latch = mFlushLatch;
-        mFlushLatch = new CountDownLatch(1);
+        mFlushLatch = null;
         if(latch != null) {
             latch.countDown();
         }
diff --git a/tests/tests/media/assets/noiseandchirps.ogg b/tests/tests/media/assets/noiseandchirps.ogg
index 1acb643..5c67a88 100644
--- a/tests/tests/media/assets/noiseandchirps.ogg
+++ b/tests/tests/media/assets/noiseandchirps.ogg
Binary files differ
diff --git a/tests/tests/media/res/raw/video_1280x720_mp4_h264_8192kbps_30fps_aac_stereo_128kbps_44100hz.mp4 b/tests/tests/media/res/raw/video_1280x720_mp4_h264_8192kbps_30fps_aac_stereo_128kbps_44100hz.mp4
new file mode 100644
index 0000000..2c9c3d3
--- /dev/null
+++ b/tests/tests/media/res/raw/video_1280x720_mp4_h264_8192kbps_30fps_aac_stereo_128kbps_44100hz.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/video_1280x720_mp4_h264_8192kbps_60fps_aac_stereo_128kbps_44100hz.mp4 b/tests/tests/media/res/raw/video_1280x720_mp4_h264_8192kbps_60fps_aac_stereo_128kbps_44100hz.mp4
new file mode 100644
index 0000000..71150af
--- /dev/null
+++ b/tests/tests/media/res/raw/video_1280x720_mp4_h264_8192kbps_60fps_aac_stereo_128kbps_44100hz.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/video_1280x720_mp4_hevc_4096kbps_30fps_aac_stereo_128kbps_44100hz.mp4 b/tests/tests/media/res/raw/video_1280x720_mp4_hevc_4096kbps_30fps_aac_stereo_128kbps_44100hz.mp4
new file mode 100644
index 0000000..18d53e6
--- /dev/null
+++ b/tests/tests/media/res/raw/video_1280x720_mp4_hevc_4096kbps_30fps_aac_stereo_128kbps_44100hz.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/video_1280x720_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz.webm b/tests/tests/media/res/raw/video_1280x720_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz.webm
deleted file mode 100644
index c8b9512..0000000
--- a/tests/tests/media/res/raw/video_1280x720_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz.webm
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/res/raw/video_1280x720_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm b/tests/tests/media/res/raw/video_1280x720_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm
new file mode 100644
index 0000000..b0cd94d
--- /dev/null
+++ b/tests/tests/media/res/raw/video_1280x720_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/video_1280x720_webm_vp8_8192kbps_30fps_vorbis_stereo_128kbps_48000hz.webm b/tests/tests/media/res/raw/video_1280x720_webm_vp8_8192kbps_30fps_vorbis_stereo_128kbps_48000hz.webm
new file mode 100644
index 0000000..bf93ab4
--- /dev/null
+++ b/tests/tests/media/res/raw/video_1280x720_webm_vp8_8192kbps_30fps_vorbis_stereo_128kbps_48000hz.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/video_1280x720_webm_vp8_8192kbps_60fps_vorbis_stereo_128kbps_48000hz.webm b/tests/tests/media/res/raw/video_1280x720_webm_vp8_8192kbps_60fps_vorbis_stereo_128kbps_48000hz.webm
new file mode 100644
index 0000000..ae0e0e3
--- /dev/null
+++ b/tests/tests/media/res/raw/video_1280x720_webm_vp8_8192kbps_60fps_vorbis_stereo_128kbps_48000hz.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/video_1280x720_webm_vp9_309kbps_25fps_vorbis_stereo_128kbps_44100hz.webm b/tests/tests/media/res/raw/video_1280x720_webm_vp9_309kbps_25fps_vorbis_stereo_128kbps_44100hz.webm
deleted file mode 100644
index 65b436a..0000000
--- a/tests/tests/media/res/raw/video_1280x720_webm_vp9_309kbps_25fps_vorbis_stereo_128kbps_44100hz.webm
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/res/raw/video_1280x720_webm_vp9_309kbps_25fps_vorbis_stereo_128kbps_48000hz.webm b/tests/tests/media/res/raw/video_1280x720_webm_vp9_309kbps_25fps_vorbis_stereo_128kbps_48000hz.webm
new file mode 100644
index 0000000..b79e3f6
--- /dev/null
+++ b/tests/tests/media/res/raw/video_1280x720_webm_vp9_309kbps_25fps_vorbis_stereo_128kbps_48000hz.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/video_1280x720_webm_vp9_4096kbps_30fps_vorbis_stereo_128kbps_48000hz.webm b/tests/tests/media/res/raw/video_1280x720_webm_vp9_4096kbps_30fps_vorbis_stereo_128kbps_48000hz.webm
new file mode 100644
index 0000000..2e769e6
--- /dev/null
+++ b/tests/tests/media/res/raw/video_1280x720_webm_vp9_4096kbps_30fps_vorbis_stereo_128kbps_48000hz.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/video_1920x1080_mp4_h264_20480kbps_30fps_aac_stereo_128kbps_44100hz.mp4 b/tests/tests/media/res/raw/video_1920x1080_mp4_h264_20480kbps_30fps_aac_stereo_128kbps_44100hz.mp4
new file mode 100644
index 0000000..35ab7a0
--- /dev/null
+++ b/tests/tests/media/res/raw/video_1920x1080_mp4_h264_20480kbps_30fps_aac_stereo_128kbps_44100hz.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/video_1920x1080_mp4_h264_20480kbps_60fps_aac_stereo_128kbps_44100hz.mp4 b/tests/tests/media/res/raw/video_1920x1080_mp4_h264_20480kbps_60fps_aac_stereo_128kbps_44100hz.mp4
new file mode 100644
index 0000000..b837dc2
--- /dev/null
+++ b/tests/tests/media/res/raw/video_1920x1080_mp4_h264_20480kbps_60fps_aac_stereo_128kbps_44100hz.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/video_1920x1080_mp4_hevc_10240kbps_30fps_aac_stereo_128kbps_44100hz.mp4 b/tests/tests/media/res/raw/video_1920x1080_mp4_hevc_10240kbps_30fps_aac_stereo_128kbps_44100hz.mp4
new file mode 100644
index 0000000..631ea04
--- /dev/null
+++ b/tests/tests/media/res/raw/video_1920x1080_mp4_hevc_10240kbps_30fps_aac_stereo_128kbps_44100hz.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/video_1920x1080_webm_vp8_20480kbps_30fps_vorbis_stereo_128kbps_48000hz.webm b/tests/tests/media/res/raw/video_1920x1080_webm_vp8_20480kbps_30fps_vorbis_stereo_128kbps_48000hz.webm
new file mode 100644
index 0000000..c147461
--- /dev/null
+++ b/tests/tests/media/res/raw/video_1920x1080_webm_vp8_20480kbps_30fps_vorbis_stereo_128kbps_48000hz.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/video_1920x1080_webm_vp8_20480kbps_60fps_vorbis_stereo_128kbps_48000hz.webm b/tests/tests/media/res/raw/video_1920x1080_webm_vp8_20480kbps_60fps_vorbis_stereo_128kbps_48000hz.webm
new file mode 100644
index 0000000..10a1a5d
--- /dev/null
+++ b/tests/tests/media/res/raw/video_1920x1080_webm_vp8_20480kbps_60fps_vorbis_stereo_128kbps_48000hz.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/video_1920x1080_webm_vp9_10240kbps_30fps_vorbis_stereo_128kbps_48000hz.webm b/tests/tests/media/res/raw/video_1920x1080_webm_vp9_10240kbps_30fps_vorbis_stereo_128kbps_48000hz.webm
new file mode 100644
index 0000000..949b327
--- /dev/null
+++ b/tests/tests/media/res/raw/video_1920x1080_webm_vp9_10240kbps_30fps_vorbis_stereo_128kbps_48000hz.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/video_320x240_mp4_h264_800kbps_30fps_aac_stereo_128kbps_44100hz.mp4 b/tests/tests/media/res/raw/video_320x240_mp4_h264_800kbps_30fps_aac_stereo_128kbps_44100hz.mp4
new file mode 100644
index 0000000..12e07ce
--- /dev/null
+++ b/tests/tests/media/res/raw/video_320x240_mp4_h264_800kbps_30fps_aac_stereo_128kbps_44100hz.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/video_320x240_webm_vp8_800kbps_30fps_vorbis_stereo_128kbps_48000hz.webm b/tests/tests/media/res/raw/video_320x240_webm_vp8_800kbps_30fps_vorbis_stereo_128kbps_48000hz.webm
new file mode 100644
index 0000000..b060ca6
--- /dev/null
+++ b/tests/tests/media/res/raw/video_320x240_webm_vp8_800kbps_30fps_vorbis_stereo_128kbps_48000hz.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/video_320x240_webm_vp9_600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm b/tests/tests/media/res/raw/video_320x240_webm_vp9_600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm
new file mode 100644
index 0000000..086f144
--- /dev/null
+++ b/tests/tests/media/res/raw/video_320x240_webm_vp9_600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/video_352x288_mp4_hevc_600kbps_30fps_aac_stereo_128kbps_44100hz.mp4 b/tests/tests/media/res/raw/video_352x288_mp4_hevc_600kbps_30fps_aac_stereo_128kbps_44100hz.mp4
new file mode 100644
index 0000000..4404877
--- /dev/null
+++ b/tests/tests/media/res/raw/video_352x288_mp4_hevc_600kbps_30fps_aac_stereo_128kbps_44100hz.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/video_3840x2160_mp4_hevc_20480kbps_30fps_aac_stereo_128kbps_44100hz.mp4 b/tests/tests/media/res/raw/video_3840x2160_mp4_hevc_20480kbps_30fps_aac_stereo_128kbps_44100hz.mp4
new file mode 100644
index 0000000..9277fbd
--- /dev/null
+++ b/tests/tests/media/res/raw/video_3840x2160_mp4_hevc_20480kbps_30fps_aac_stereo_128kbps_44100hz.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/video_3840x2160_webm_vp9_20480kbps_30fps_vorbis_stereo_128kbps_48000hz.webm b/tests/tests/media/res/raw/video_3840x2160_webm_vp9_20480kbps_30fps_vorbis_stereo_128kbps_48000hz.webm
new file mode 100644
index 0000000..d6ed441
--- /dev/null
+++ b/tests/tests/media/res/raw/video_3840x2160_webm_vp9_20480kbps_30fps_vorbis_stereo_128kbps_48000hz.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz.webm b/tests/tests/media/res/raw/video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz.webm
deleted file mode 100644
index f64aec3..0000000
--- a/tests/tests/media/res/raw/video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz.webm
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/res/raw/video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm b/tests/tests/media/res/raw/video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm
new file mode 100644
index 0000000..c101291
--- /dev/null
+++ b/tests/tests/media/res/raw/video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_44100hz.webm b/tests/tests/media/res/raw/video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_44100hz.webm
deleted file mode 100644
index 59b0c44..0000000
--- a/tests/tests/media/res/raw/video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_44100hz.webm
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/res/raw/video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm b/tests/tests/media/res/raw/video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm
new file mode 100644
index 0000000..0a33e54
--- /dev/null
+++ b/tests/tests/media/res/raw/video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/video_640x360_webm_vp8_2048kbps_30fps_vorbis_stereo_128kbps_48000hz.webm b/tests/tests/media/res/raw/video_640x360_webm_vp8_2048kbps_30fps_vorbis_stereo_128kbps_48000hz.webm
new file mode 100644
index 0000000..60860f1
--- /dev/null
+++ b/tests/tests/media/res/raw/video_640x360_webm_vp8_2048kbps_30fps_vorbis_stereo_128kbps_48000hz.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/video_640x360_webm_vp9_1600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm b/tests/tests/media/res/raw/video_640x360_webm_vp9_1600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm
new file mode 100644
index 0000000..3f02f9a
--- /dev/null
+++ b/tests/tests/media/res/raw/video_640x360_webm_vp9_1600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/video_720x480_mp4_h264_2048kbps_30fps_aac_stereo_128kbps_44100hz.mp4 b/tests/tests/media/res/raw/video_720x480_mp4_h264_2048kbps_30fps_aac_stereo_128kbps_44100hz.mp4
new file mode 100644
index 0000000..055e217
--- /dev/null
+++ b/tests/tests/media/res/raw/video_720x480_mp4_h264_2048kbps_30fps_aac_stereo_128kbps_44100hz.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/video_720x480_mp4_hevc_1638kbps_30fps_aac_stereo_128kbps_44100hz.mp4 b/tests/tests/media/res/raw/video_720x480_mp4_hevc_1638kbps_30fps_aac_stereo_128kbps_44100hz.mp4
new file mode 100644
index 0000000..87c6897
--- /dev/null
+++ b/tests/tests/media/res/raw/video_720x480_mp4_hevc_1638kbps_30fps_aac_stereo_128kbps_44100hz.mp4
Binary files differ
diff --git a/tests/tests/media/src/android/media/cts/AdaptivePlaybackTest.java b/tests/tests/media/src/android/media/cts/AdaptivePlaybackTest.java
index dbe2c92..e6c4d8a 100644
--- a/tests/tests/media/src/android/media/cts/AdaptivePlaybackTest.java
+++ b/tests/tests/media/src/android/media/cts/AdaptivePlaybackTest.java
@@ -50,7 +50,7 @@
     public Iterable<Codec> H264(CodecFactory factory) {
         return factory.createCodecList(
                 mContext,
-                "video/avc",
+                MediaFormat.MIMETYPE_VIDEO_AVC,
                 "OMX.google.h264.decoder",
                 R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz,
                 R.raw.video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz);
@@ -59,7 +59,7 @@
     public Iterable<Codec> HEVC(CodecFactory factory) {
         return factory.createCodecList(
                 mContext,
-                "video/hevc",
+                MediaFormat.MIMETYPE_VIDEO_HEVC,
                 "OMX.google.hevc.decoder",
                 R.raw.video_640x360_mp4_hevc_450kbps_30fps_aac_stereo_128kbps_48000hz,
                 R.raw.video_1280x720_mp4_hevc_1150kbps_30fps_aac_stereo_128kbps_48000hz);
@@ -68,7 +68,7 @@
     public Iterable<Codec> H263(CodecFactory factory) {
         return factory.createCodecList(
                 mContext,
-                "video/3gpp",
+                MediaFormat.MIMETYPE_VIDEO_H263,
                 "OMX.google.h263.decoder",
                 R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz,
                 R.raw.video_352x288_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz);
@@ -77,7 +77,7 @@
     public Iterable<Codec> Mpeg4(CodecFactory factory) {
         return factory.createCodecList(
                 mContext,
-                "video/mp4v-es",
+                MediaFormat.MIMETYPE_VIDEO_MPEG4,
                 "OMX.google.mpeg4.decoder",
 
                 R.raw.video_1280x720_mp4_mpeg4_1000kbps_25fps_aac_stereo_128kbps_44100hz,
@@ -87,19 +87,19 @@
     public Iterable<Codec> VP8(CodecFactory factory) {
         return factory.createCodecList(
                 mContext,
-                "video/x-vnd.on2.vp8",
+                MediaFormat.MIMETYPE_VIDEO_VP8,
                 "OMX.google.vp8.decoder",
-                R.raw.video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz,
-                R.raw.video_1280x720_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz);
+                R.raw.video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_48000hz,
+                R.raw.video_1280x720_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_48000hz);
     }
 
     public Iterable<Codec> VP9(CodecFactory factory) {
         return factory.createCodecList(
                 mContext,
-                "video/x-vnd.on2.vp9",
+                MediaFormat.MIMETYPE_VIDEO_VP9,
                 "OMX.google.vp9.decoder",
-                R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_44100hz,
-                R.raw.video_1280x720_webm_vp9_309kbps_25fps_vorbis_stereo_128kbps_44100hz);
+                R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz,
+                R.raw.video_1280x720_webm_vp9_309kbps_25fps_vorbis_stereo_128kbps_48000hz);
     }
 
     CodecFactory ALL = new CodecFactory();
@@ -1288,9 +1288,8 @@
             }
 
             /* enumerate codecs */
-            int codecCount = MediaCodecList.getCodecCount();
-            for (int codecIx = 0; codecIx < codecCount; codecIx++) {
-                MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(codecIx);
+            MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+            for (MediaCodecInfo codecInfo : mcl.getCodecInfos()) {
                 if (codecInfo.isEncoder()) {
                     continue;
                 }
@@ -1311,11 +1310,13 @@
 
             /* test if the explicitly named codec is present on the system */
             if (explicitCodecName != null) {
-                MediaCodec codec = MediaCodec.createByCodecName(explicitCodecName);
-                if (codec != null) {
-                    codec.release();
-                    add(new Codec(explicitCodecName, null, mediaList));
-                }
+                try {
+                    MediaCodec codec = MediaCodec.createByCodecName(explicitCodecName);
+                    if (codec != null) {
+                        codec.release();
+                        add(new Codec(explicitCodecName, null, mediaList));
+                    }
+                } catch (Exception e) {}
             }
         } catch (Throwable t) {
             Log.wtf("Constructor failed", t);
diff --git a/tests/tests/media/src/android/media/cts/AudioManagerTest.java b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
index e65fb0b..f58e6ab 100644
--- a/tests/tests/media/src/android/media/cts/AudioManagerTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
@@ -38,10 +38,10 @@
 
 import com.android.cts.media.R;
 
-
 import android.content.Context;
 import android.content.res.Resources;
 import android.media.AudioManager;
+import android.content.pm.PackageManager;
 import android.media.MediaPlayer;
 import android.os.Vibrator;
 import android.provider.Settings;
@@ -49,13 +49,19 @@
 import android.test.AndroidTestCase;
 import android.view.SoundEffectConstants;
 
+import java.util.TreeMap;
+
 public class AudioManagerTest extends AndroidTestCase {
 
     private final static int MP3_TO_PLAY = R.raw.testmp3;
     private final static long TIME_TO_PLAY = 2000;
     private AudioManager mAudioManager;
     private boolean mHasVibrator;
+    private boolean mUseMasterVolume;
     private boolean mUseFixedVolume;
+    private int[] mMasterVolumeRamp;
+    private TreeMap<Integer, Integer> mMasterVolumeMap = new TreeMap<Integer, Integer>();
+    private boolean mIsTelevision;
 
     @Override
     protected void setUp() throws Exception {
@@ -63,8 +69,20 @@
         mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
         Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
         mHasVibrator = (vibrator != null) && vibrator.hasVibrator();
+        mUseMasterVolume = mContext.getResources().getBoolean(
+                Resources.getSystem().getIdentifier("config_useMasterVolume", "bool", "android"));
         mUseFixedVolume = mContext.getResources().getBoolean(
                 Resources.getSystem().getIdentifier("config_useFixedVolume", "bool", "android"));
+        mMasterVolumeRamp = mContext.getResources().getIntArray(
+                Resources.getSystem().getIdentifier("config_masterVolumeRamp", "array", "android"));
+        assertTrue((mMasterVolumeRamp.length > 0) && (mMasterVolumeRamp.length % 2 == 0));
+        for (int i = 0; i < mMasterVolumeRamp.length; i+=2) {
+            mMasterVolumeMap.put(mMasterVolumeRamp[i], mMasterVolumeRamp[i+1]);
+        }
+        PackageManager packageManager = mContext.getPackageManager();
+        mIsTelevision = packageManager != null
+                && (packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
+                        || packageManager.hasSystemFeature(PackageManager.FEATURE_TELEVISION));
     }
 
     public void testMicrophoneMute() throws Exception {
@@ -171,7 +189,7 @@
     }
 
     public void testVibrateNotification() throws Exception {
-        if (mUseFixedVolume) {
+        if (mUseFixedVolume || !mHasVibrator) {
             return;
         }
         // VIBRATE_SETTING_ON
@@ -232,7 +250,7 @@
     }
 
     public void testVibrateRinger() throws Exception {
-        if (mUseFixedVolume) {
+        if (mUseFixedVolume || !mHasVibrator) {
             return;
         }
         // VIBRATE_TYPE_RINGER
@@ -298,14 +316,16 @@
         assertEquals(RINGER_MODE_NORMAL, mAudioManager.getRingerMode());
 
         mAudioManager.setRingerMode(RINGER_MODE_SILENT);
-        if (mUseFixedVolume) {
+        // AudioService#setRingerMode() has:
+        // if (isTelevision) return;
+        if (mUseFixedVolume || mIsTelevision) {
             assertEquals(RINGER_MODE_NORMAL, mAudioManager.getRingerMode());
         } else {
             assertEquals(RINGER_MODE_SILENT, mAudioManager.getRingerMode());
         }
 
         mAudioManager.setRingerMode(RINGER_MODE_VIBRATE);
-        if (mUseFixedVolume) {
+        if (mUseFixedVolume || mIsTelevision) {
             assertEquals(RINGER_MODE_NORMAL, mAudioManager.getRingerMode());
         } else {
             assertEquals(mHasVibrator ? RINGER_MODE_VIBRATE : RINGER_MODE_SILENT,
@@ -314,6 +334,7 @@
     }
 
     public void testVolume() throws Exception {
+        int volume, volumeDelta;
         int[] streams = { AudioManager.STREAM_ALARM,
                           AudioManager.STREAM_MUSIC,
                           AudioManager.STREAM_VOICE_CALL,
@@ -356,22 +377,32 @@
             mAudioManager.adjustStreamVolume(streams[i], ADJUST_RAISE, 0);
             assertEquals(maxVolume, mAudioManager.getStreamVolume(streams[i]));
 
+            volumeDelta = getVolumeDelta(mAudioManager.getStreamVolume(streams[i]));
             mAudioManager.adjustSuggestedStreamVolume(ADJUST_LOWER, streams[i], 0);
-            assertEquals(maxVolume - 1, mAudioManager.getStreamVolume(streams[i]));
+            assertEquals(maxVolume - volumeDelta, mAudioManager.getStreamVolume(streams[i]));
 
             // volume lower
             mAudioManager.setStreamVolume(streams[i], maxVolume, 0);
-            for (int k = maxVolume; k > 0; k--) {
+            volume = mAudioManager.getStreamVolume(streams[i]);
+            while (volume > 0) {
+                volumeDelta = getVolumeDelta(mAudioManager.getStreamVolume(streams[i]));
                 mAudioManager.adjustStreamVolume(streams[i], ADJUST_LOWER, 0);
-                assertEquals(k - 1, mAudioManager.getStreamVolume(streams[i]));
+                assertEquals(Math.max(0, volume - volumeDelta),
+                             mAudioManager.getStreamVolume(streams[i]));
+                volume = mAudioManager.getStreamVolume(streams[i]);
             }
 
             mAudioManager.adjustStreamVolume(streams[i], ADJUST_SAME, 0);
+
             // volume raise
             mAudioManager.setStreamVolume(streams[i], 1, 0);
-            for (int k = 1; k < maxVolume; k++) {
+            volume = mAudioManager.getStreamVolume(streams[i]);
+            while (volume < maxVolume) {
+                volumeDelta = getVolumeDelta(mAudioManager.getStreamVolume(streams[i]));
                 mAudioManager.adjustStreamVolume(streams[i], ADJUST_RAISE, 0);
-                assertEquals(1 + k, mAudioManager.getStreamVolume(streams[i]));
+                assertEquals(Math.min(volume + volumeDelta, maxVolume),
+                             mAudioManager.getStreamVolume(streams[i]));
+                volume = mAudioManager.getStreamVolume(streams[i]);
             }
 
             // volume same
@@ -406,18 +437,20 @@
         }
 
         // adjust volume as ADJUST_RAISE
-        mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
-        for (int k = 0; k < maxMusicVolume - 1; k++) {
-            mAudioManager.adjustVolume(ADJUST_RAISE, 0);
-            assertEquals(2 + k, mAudioManager.getStreamVolume(STREAM_MUSIC));
-        }
+        mAudioManager.setStreamVolume(STREAM_MUSIC, 0, 0);
+        volumeDelta = getVolumeDelta(mAudioManager.getStreamVolume(STREAM_MUSIC));
+        mAudioManager.adjustVolume(ADJUST_RAISE, 0);
+        assertEquals(Math.min(volumeDelta, maxMusicVolume),
+                     mAudioManager.getStreamVolume(STREAM_MUSIC));
 
         // adjust volume as ADJUST_LOWER
         mAudioManager.setStreamVolume(STREAM_MUSIC, maxMusicVolume, 0);
         maxMusicVolume = mAudioManager.getStreamVolume(STREAM_MUSIC);
-
+        volumeDelta = getVolumeDelta(mAudioManager.getStreamVolume(STREAM_MUSIC));
         mAudioManager.adjustVolume(ADJUST_LOWER, 0);
-        assertEquals(maxMusicVolume - 1, mAudioManager.getStreamVolume(STREAM_MUSIC));
+        assertEquals(Math.max(0, maxMusicVolume - volumeDelta),
+                     mAudioManager.getStreamVolume(STREAM_MUSIC));
+
         mp.stop();
         mp.release();
         Thread.sleep(TIME_TO_PLAY);
@@ -432,4 +465,13 @@
         mAudioManager.setRingerMode(-3007);
         assertEquals(ringerMode, mAudioManager.getRingerMode());
     }
+
+    private int getVolumeDelta(int volume) {
+        if (!mUseMasterVolume) {
+            return 1;
+        }
+        int volumeDelta = mMasterVolumeMap.floorEntry(volume).getValue();
+        assertTrue(volumeDelta > 0);
+        return volumeDelta;
+    }
 }
diff --git a/tests/tests/media/src/android/media/cts/AudioTrackTest.java b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
index d01ecec..a342c37 100644
--- a/tests/tests/media/src/android/media/cts/AudioTrackTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
@@ -16,6 +16,7 @@
 
 package android.media.cts;
 
+import android.content.pm.PackageManager;
 import android.cts.util.CtsAndroidTestCase;
 import android.media.AudioFormat;
 import android.media.AudioManager;
@@ -1541,7 +1542,16 @@
         }
     }
 
+    private boolean hasAudioOutput() {
+        return getContext().getPackageManager()
+            .hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT);
+    }
+
     public void testGetTimestamp() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+        
         // constants for test
         final String TEST_NAME = "testGetTimestamp";
         final int TEST_SR = 22050;
diff --git a/tests/tests/media/src/android/media/cts/CamcorderProfileTest.java b/tests/tests/media/src/android/media/cts/CamcorderProfileTest.java
index 8130a9a..dd96c2c 100644
--- a/tests/tests/media/src/android/media/cts/CamcorderProfileTest.java
+++ b/tests/tests/media/src/android/media/cts/CamcorderProfileTest.java
@@ -25,12 +25,47 @@
 import android.test.AndroidTestCase;
 import android.util.Log;
 
+import java.util.Arrays;
 import java.util.List;
 
 public class CamcorderProfileTest extends AndroidTestCase {
 
     private static final String TAG = "CamcorderProfileTest";
     private static final int MIN_HIGH_SPEED_FPS = 100;
+    private static final Integer[] ALL_SUPPORTED_QUALITIES = {
+        CamcorderProfile.QUALITY_LOW,
+        CamcorderProfile.QUALITY_HIGH,
+        CamcorderProfile.QUALITY_QCIF,
+        CamcorderProfile.QUALITY_CIF,
+        CamcorderProfile.QUALITY_480P,
+        CamcorderProfile.QUALITY_720P,
+        CamcorderProfile.QUALITY_1080P,
+        CamcorderProfile.QUALITY_QVGA,
+        CamcorderProfile.QUALITY_2160P,
+        CamcorderProfile.QUALITY_TIME_LAPSE_LOW,
+        CamcorderProfile.QUALITY_TIME_LAPSE_HIGH,
+        CamcorderProfile.QUALITY_TIME_LAPSE_QCIF,
+        CamcorderProfile.QUALITY_TIME_LAPSE_CIF,
+        CamcorderProfile.QUALITY_TIME_LAPSE_480P,
+        CamcorderProfile.QUALITY_TIME_LAPSE_720P,
+        CamcorderProfile.QUALITY_TIME_LAPSE_1080P,
+        CamcorderProfile.QUALITY_TIME_LAPSE_QVGA,
+        CamcorderProfile.QUALITY_TIME_LAPSE_2160P,
+        CamcorderProfile.QUALITY_HIGH_SPEED_LOW,
+        CamcorderProfile.QUALITY_HIGH_SPEED_HIGH,
+        CamcorderProfile.QUALITY_HIGH_SPEED_480P,
+        CamcorderProfile.QUALITY_HIGH_SPEED_720P,
+        CamcorderProfile.QUALITY_HIGH_SPEED_1080P,
+        CamcorderProfile.QUALITY_HIGH_SPEED_2160P
+    };
+    private static final int LAST_QUALITY = CamcorderProfile.QUALITY_2160P;
+    private static final int LAST_TIMELAPSE_QUALITY = CamcorderProfile.QUALITY_TIME_LAPSE_2160P;
+    private static final int LAST_HIGH_SPEED_QUALITY = CamcorderProfile.QUALITY_HIGH_SPEED_2160P;
+    private static final Integer[] UNKNOWN_QUALITIES = {
+        LAST_QUALITY + 1, // Unknown normal profile quality
+        LAST_TIMELAPSE_QUALITY + 1, // Unknown timelapse profile quality
+        LAST_HIGH_SPEED_QUALITY + 1 // Unknown high speed timelapse profile quality
+    };
 
     // Uses get without id if cameraId == -1 and get with id otherwise.
     private CamcorderProfile getWithOptionalId(int quality, int cameraId) {
@@ -59,27 +94,7 @@
             profile.audioSampleRate,
             profile.audioChannels));
         assertTrue(profile.duration > 0);
-        assertTrue(profile.quality == CamcorderProfile.QUALITY_LOW ||
-                   profile.quality == CamcorderProfile.QUALITY_HIGH ||
-                   profile.quality == CamcorderProfile.QUALITY_QCIF ||
-                   profile.quality == CamcorderProfile.QUALITY_CIF ||
-                   profile.quality == CamcorderProfile.QUALITY_480P ||
-                   profile.quality == CamcorderProfile.QUALITY_720P ||
-                   profile.quality == CamcorderProfile.QUALITY_1080P ||
-                   profile.quality == CamcorderProfile.QUALITY_2160P ||
-                   profile.quality == CamcorderProfile.QUALITY_TIME_LAPSE_LOW ||
-                   profile.quality == CamcorderProfile.QUALITY_TIME_LAPSE_HIGH ||
-                   profile.quality == CamcorderProfile.QUALITY_TIME_LAPSE_QCIF ||
-                   profile.quality == CamcorderProfile.QUALITY_TIME_LAPSE_CIF ||
-                   profile.quality == CamcorderProfile.QUALITY_TIME_LAPSE_480P ||
-                   profile.quality == CamcorderProfile.QUALITY_TIME_LAPSE_720P ||
-                   profile.quality == CamcorderProfile.QUALITY_TIME_LAPSE_1080P ||
-                   profile.quality == CamcorderProfile.QUALITY_TIME_LAPSE_2160P ||
-                   profile.quality == CamcorderProfile.QUALITY_HIGH_SPEED_LOW ||
-                   profile.quality == CamcorderProfile.QUALITY_HIGH_SPEED_HIGH ||
-                   profile.quality == CamcorderProfile.QUALITY_HIGH_SPEED_480P ||
-                   profile.quality == CamcorderProfile.QUALITY_HIGH_SPEED_720P ||
-                   profile.quality == CamcorderProfile.QUALITY_HIGH_SPEED_1080P);
+        assertTrue(Arrays.asList(ALL_SUPPORTED_QUALITIES).contains(profile.quality));
         assertTrue(profile.videoBitRate > 0);
         assertTrue(profile.videoFrameRate > 0);
         assertTrue(profile.videoFrameWidth > 0);
@@ -233,19 +248,30 @@
 
         final List<Size> videoSizes = getSupportedVideoSizes(cameraId);
 
-        CamcorderProfile lowProfile =
-            getWithOptionalId(CamcorderProfile.QUALITY_LOW, cameraId);
-        CamcorderProfile highProfile =
-            getWithOptionalId(CamcorderProfile.QUALITY_HIGH, cameraId);
-        checkProfile(lowProfile, videoSizes);
-        checkProfile(highProfile, videoSizes);
+        /**
+         * Check all possible supported profiles: get profile should work, and the profile
+         * should be sane. Note that, timelapse and high speed video sizes may not be listed
+         * as supported video sizes from camera, skip the size check.
+         */
+        for (Integer quality : ALL_SUPPORTED_QUALITIES) {
+            if (CamcorderProfile.hasProfile(cameraId, quality) || isProfileMandatory(quality)) {
+                List<Size> videoSizesToCheck = null;
+                if (quality >= CamcorderProfile.QUALITY_LOW &&
+                                quality <= CamcorderProfile.QUALITY_2160P) {
+                    videoSizesToCheck = videoSizes;
+                }
+                CamcorderProfile profile = getWithOptionalId(quality, cameraId);
+                checkProfile(profile, videoSizesToCheck);
+            }
+        }
 
-        CamcorderProfile lowTimeLapseProfile =
-            getWithOptionalId(CamcorderProfile.QUALITY_TIME_LAPSE_LOW, cameraId);
-        CamcorderProfile highTimeLapseProfile =
-            getWithOptionalId(CamcorderProfile.QUALITY_TIME_LAPSE_HIGH, cameraId);
-        checkProfile(lowTimeLapseProfile, null);
-        checkProfile(highTimeLapseProfile, null);
+        /**
+         * Check unknown profiles: hasProfile() should return false.
+         */
+        for (Integer quality : UNKNOWN_QUALITIES) {
+            assertFalse("Unknown profile quality " + quality + " shouldn't be supported by camera "
+                    + cameraId, CamcorderProfile.hasProfile(cameraId, quality));
+        }
 
         // High speed low and high profile are optional,
         // but they should be both present or missing.
@@ -288,8 +314,17 @@
 
         int[] specificHighSpeedProfileQualities = {CamcorderProfile.QUALITY_HIGH_SPEED_480P,
                                                    CamcorderProfile.QUALITY_HIGH_SPEED_720P,
-                                                   CamcorderProfile.QUALITY_HIGH_SPEED_1080P};
+                                                   CamcorderProfile.QUALITY_HIGH_SPEED_1080P,
+                                                   CamcorderProfile.QUALITY_HIGH_SPEED_2160P};
 
+        CamcorderProfile lowProfile =
+                getWithOptionalId(CamcorderProfile.QUALITY_LOW, cameraId);
+        CamcorderProfile highProfile =
+                getWithOptionalId(CamcorderProfile.QUALITY_HIGH, cameraId);
+        CamcorderProfile lowTimeLapseProfile =
+                getWithOptionalId(CamcorderProfile.QUALITY_TIME_LAPSE_LOW, cameraId);
+        CamcorderProfile highTimeLapseProfile =
+                getWithOptionalId(CamcorderProfile.QUALITY_TIME_LAPSE_HIGH, cameraId);
         checkSpecificProfiles(cameraId, lowProfile, highProfile,
                 specificProfileQualities, videoSizes);
         checkSpecificProfiles(cameraId, lowTimeLapseProfile, highTimeLapseProfile,
@@ -342,4 +377,11 @@
         Log.e(TAG, "Size (" + width + "x" + height + ") is not supported");
         return false;
     }
+
+    private boolean isProfileMandatory(int quality) {
+        return (quality == CamcorderProfile.QUALITY_LOW) ||
+                (quality == CamcorderProfile.QUALITY_HIGH) ||
+                (quality == CamcorderProfile.QUALITY_TIME_LAPSE_LOW) ||
+                (quality == CamcorderProfile.QUALITY_TIME_LAPSE_HIGH);
+    }
 }
diff --git a/tests/tests/media/src/android/media/cts/ClearKeySystemTest.java b/tests/tests/media/src/android/media/cts/ClearKeySystemTest.java
index 673c1d7..c837d0a 100644
--- a/tests/tests/media/src/android/media/cts/ClearKeySystemTest.java
+++ b/tests/tests/media/src/android/media/cts/ClearKeySystemTest.java
@@ -24,6 +24,7 @@
 import android.media.MediaCodecList;
 import android.media.MediaDrm;
 import android.media.MediaDrmException;
+import android.media.MediaFormat;
 import android.media.CamcorderProfile;
 import android.net.Uri;
 import android.os.Environment;
@@ -63,7 +64,7 @@
     private static final int VIDEO_WIDTH = 1280;
     private static final int VIDEO_HEIGHT = 720;
     private static final long PLAY_TIME_MS = TimeUnit.MILLISECONDS.convert(1, TimeUnit.MINUTES);
-    private static final String MIME_VIDEO_AVC = "video/avc";
+    private static final String MIME_VIDEO_AVC = MediaFormat.MIMETYPE_VIDEO_AVC;
 
     private static final Uri AUDIO_URL = Uri.parse(
             "http://yt-dash-mse-test.commondatastorage.googleapis.com/media/car_cenc-20120827-8c.mp4");
@@ -303,109 +304,16 @@
             }
         }
 
-        CodecCapabilities cap;
-        int highestProfileLevel = 0;
-        MediaCodecInfo codecInfo;
-
-        for (int i = 0; i < MediaCodecList.getCodecCount(); i++) {
-            codecInfo = MediaCodecList.getCodecInfoAt(i);
-            if (codecInfo.isEncoder()) {
-                continue;
-            }
-
-            String[] types = codecInfo.getSupportedTypes();
-            for (int j = 0; j < types.length; ++j) {
-                if (!types[j].equalsIgnoreCase(MIME_VIDEO_AVC)) {
-                    continue;
-                }
-
-                Log.d(TAG, "codec: " + codecInfo.getName() + "types: " + types[j]);
-                cap = codecInfo.getCapabilitiesForType(types[j]);
-                for (CodecProfileLevel profileLevel : cap.profileLevels) {
-                    Log.i(TAG, "codec " + codecInfo.getName() + ", level " + profileLevel.level);
-                    if (profileLevel.level > highestProfileLevel) {
-                        highestProfileLevel = profileLevel.level;
-                    }
-                }
-                Log.i(TAG, "codec " + codecInfo.getName() + ", highest level " + highestProfileLevel);
-            }
+        MediaFormat format = MediaFormat.createVideoFormat(
+                MIME_VIDEO_AVC, videoWidth, videoHeight);
+        // using secure codec even though it is clear key DRM
+        format.setFeatureEnabled(CodecCapabilities.FEATURE_SecurePlayback, true);
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS);
+        if (mcl.findDecoderForFormat(format) == null) {
+            Log.i(TAG, "could not find codec for " + format);
+            return false;
         }
-
-        // AVCLevel and its resolution is taken from http://en.wikipedia.org/wiki/H.264/MPEG-4_AVC
-        switch(highestProfileLevel) {
-        case CodecProfileLevel.AVCLevel1:
-        case CodecProfileLevel.AVCLevel1b:
-            return (videoWidth <= 176 && videoHeight <= 144);
-        case CodecProfileLevel.AVCLevel11:
-        case CodecProfileLevel.AVCLevel12:
-        case CodecProfileLevel.AVCLevel13:
-        case CodecProfileLevel.AVCLevel2:
-            return (videoWidth <= 352 && videoHeight <= 288);
-        case CodecProfileLevel.AVCLevel21:
-            return (videoWidth <= 352 && videoHeight <= 576);
-        case CodecProfileLevel.AVCLevel22:
-        case CodecProfileLevel.AVCLevel3:
-            return (videoWidth <= 720 && videoHeight <= 576);
-        case CodecProfileLevel.AVCLevel31:
-            return (videoWidth <= 1280 && videoHeight <= 720);
-        case CodecProfileLevel.AVCLevel32:
-            return (videoWidth <= 1280 && videoHeight <= 1024);
-        case CodecProfileLevel.AVCLevel4:
-        case CodecProfileLevel.AVCLevel41:
-            // 1280 x 720
-            // 1920 x 1080
-            // 2048 x 1024
-            if (videoWidth <= 1920) {
-                return (videoHeight <= 1080);
-            } else if (videoWidth <= 2048) {
-                return (videoHeight <= 1024);
-            } else {
-                return false;
-            }
-        case CodecProfileLevel.AVCLevel42:
-            return (videoWidth <= 2048 && videoHeight <= 1080);
-        case CodecProfileLevel.AVCLevel5:
-            // 1920 x 1080
-            // 2048 x 1024
-            // 2048 x 1080
-            // 2560 x 1920
-            // 3672 x 1536
-            if (videoWidth <= 1920) {
-                return (videoHeight <= 1080);
-            } else if (videoWidth <= 2048) {
-                return (videoHeight <= 1080);
-            } else if (videoWidth <= 2560) {
-                return (videoHeight <= 1920);
-            } else if (videoWidth <= 3672) {
-                return (videoHeight <= 1536);
-            } else {
-                return false;
-            }
-        case CodecProfileLevel.AVCLevel51:
-        default:  // any future extension will cap at level 5.1
-            // 1920 x 1080
-            // 2560 x 1920
-            // 3840 x 2160
-            // 4096 x 2048
-            // 4096 x 2160
-            // 4096 x 2304
-            if (videoWidth <= 1920) {
-                return (videoHeight <= 1080);
-            } else if (videoWidth <= 2560) {
-                return (videoHeight <= 1920);
-            } else if (videoWidth <= 3840) {
-                return (videoHeight <= 2160);
-            } else if (videoWidth <= 4096) {
-                return (videoHeight <= 2304);
-            } else {
-                return false;
-            }
-        }
-    }
-
-    private boolean hasAudioOutput() {
-        return getInstrumentation().getTargetContext().getPackageManager()
-            .hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT);
+        return true;
     }
 
     /**
@@ -415,7 +323,7 @@
         if (!hasAudioOutput()) {
             return;
         }
-        
+
         MediaDrm drm = startDrm();
         if (null == drm) {
             throw new Error("Failed to create drm.");
diff --git a/tests/tests/media/src/android/media/cts/DecodeEditEncodeTest.java b/tests/tests/media/src/android/media/cts/DecodeEditEncodeTest.java
index 9ee3118..1ceb025 100644
--- a/tests/tests/media/src/android/media/cts/DecodeEditEncodeTest.java
+++ b/tests/tests/media/src/android/media/cts/DecodeEditEncodeTest.java
@@ -53,7 +53,8 @@
     private static final boolean DEBUG_SAVE_FILE = false;   // save copy of encoded movie
 
     // parameters for the encoder
-    private static final String MIME_TYPE = "video/avc";    // H.264 Advanced Video Coding
+                                                            // H.264 Advanced Video Coding
+    private static final String MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_AVC;
     private static final int FRAME_RATE = 15;               // 15fps
     private static final int IFRAME_INTERVAL = 10;          // 10 seconds between I-frames
 
@@ -806,7 +807,7 @@
 
 
     /**
-     * The elementary stream coming out of the "video/avc" encoder needs to be fed back into
+     * The elementary stream coming out of the encoder needs to be fed back into
      * the decoder one chunk at a time.  If we just wrote the data to a file, we would lose
      * the information about chunk boundaries.  This class stores the encoded data in memory,
      * retaining the chunk organization.
diff --git a/tests/tests/media/src/android/media/cts/DecoderTest.java b/tests/tests/media/src/android/media/cts/DecoderTest.java
index ba70f32..bf5aa4a 100644
--- a/tests/tests/media/src/android/media/cts/DecoderTest.java
+++ b/tests/tests/media/src/android/media/cts/DecoderTest.java
@@ -18,8 +18,11 @@
 
 import com.android.cts.media.R;
 
+import android.content.Context;
+import android.content.pm.PackageManager;
 import android.content.res.AssetFileDescriptor;
 import android.content.res.Resources;
+import android.cts.util.MediaUtils;
 import android.graphics.ImageFormat;
 import android.media.Image;
 import android.media.MediaCodec;
@@ -80,14 +83,15 @@
 
     // TODO: add similar tests for other audio and video formats
     public void testBug11696552() throws Exception {
-        MediaCodec mMediaCodec = MediaCodec.createDecoderByType("audio/mp4a-latm");
-        MediaFormat mFormat = MediaFormat.createAudioFormat("audio/mp4a-latm", 48000, 2);
+        MediaCodec mMediaCodec = MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
+        MediaFormat mFormat = MediaFormat.createAudioFormat(
+                MediaFormat.MIMETYPE_AUDIO_AAC, 48000 /* frequency */, 2 /* channels */);
         mFormat.setByteBuffer("csd-0", ByteBuffer.wrap( new byte [] {0x13, 0x10} ));
         mFormat.setInteger(MediaFormat.KEY_IS_ADTS, 1);
         mMediaCodec.configure(mFormat, null, null, 0);
         mMediaCodec.start();
         int index = mMediaCodec.dequeueInputBuffer(250000);
-        mMediaCodec.queueInputBuffer(index, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM );
+        mMediaCodec.queueInputBuffer(index, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
         MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
         mMediaCodec.dequeueOutputBuffer(info, 250000);
     }
@@ -164,17 +168,26 @@
     }
 
     public void testBFrames() throws Exception {
-        testBFrames(R.raw.video_h264_main_b_frames);
-        testBFrames(R.raw.video_h264_main_b_frames_frag);
+        int testsRun =
+            testBFrames(R.raw.video_h264_main_b_frames) +
+            testBFrames(R.raw.video_h264_main_b_frames_frag);
+        if (testsRun == 0) {
+            MediaUtils.skipTest("no codec found");
+        }
     }
 
-    public void testBFrames(int res) throws Exception {
+    public int testBFrames(int res) throws Exception {
         AssetFileDescriptor fd = mResources.openRawResourceFd(res);
         MediaExtractor ex = new MediaExtractor();
         ex.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
         MediaFormat format = ex.getTrackFormat(0);
         String mime = format.getString(MediaFormat.KEY_MIME);
         assertTrue("not a video track. Wrong test file?", mime.startsWith("video/"));
+        if (!MediaUtils.canDecode(format)) {
+            ex.release();
+            fd.close();
+            return 0; // skip
+        }
         MediaCodec dec = MediaCodec.createDecoderByType(mime);
         Surface s = getActivity().getSurfaceHolder().getSurface();
         dec.configure(format, s, null, 0);
@@ -217,6 +230,8 @@
         assertTrue("extractor timestamps were ordered, wrong test file?", inputoutoforder);
         dec.release();
         ex.release();
+        fd.close();
+        return 1;
       }
 
     private void testTrackSelection(int resid) throws Exception {
@@ -839,130 +854,246 @@
         return numsamples;
     }
 
-    public void testCodecBasicH264() throws Exception {
-        Surface s = getActivity().getSurfaceHolder().getSurface();
-        int frames1 = countFrames(
-                R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz,
-                RESET_MODE_NONE, -1 /* eosframe */, s);
-        assertEquals("wrong number of frames decoded", 240, frames1);
+    private void testDecode(int testVideo, int frameNum) throws Exception {
+        if (!MediaUtils.checkCodecForResource(mContext, testVideo, 0 /* track */)) {
+            return; // skip
+        }
 
-        int frames2 = countFrames(
-                R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz,
-                RESET_MODE_NONE, -1 /* eosframe */, null);
+        // Decode to Surface.
+        Surface s = getActivity().getSurfaceHolder().getSurface();
+        int frames1 = countFrames(testVideo, RESET_MODE_NONE, -1 /* eosframe */, s);
+        assertEquals("wrong number of frames decoded", frameNum, frames1);
+
+        // Decode to buffer.
+        int frames2 = countFrames(testVideo, RESET_MODE_NONE, -1 /* eosframe */, null);
         assertEquals("different number of frames when using Surface", frames1, frames2);
     }
 
+    public void testCodecBasicH264() throws Exception {
+        testDecode(R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz, 240);
+    }
+
     public void testCodecBasicHEVC() throws Exception {
-        Surface s = getActivity().getSurfaceHolder().getSurface();
-        int frames1 = countFrames(
-                R.raw.video_1280x720_mp4_hevc_1150kbps_30fps_aac_stereo_128kbps_48000hz,
-                RESET_MODE_NONE, -1 /* eosframe */, s);
-        assertEquals("wrong number of frames decoded", 300, frames1);
-
-        int frames2 = countFrames(
-                R.raw.video_1280x720_mp4_hevc_1150kbps_30fps_aac_stereo_128kbps_48000hz,
-                RESET_MODE_NONE, -1 /* eosframe */, null);
-        assertEquals("different number of frames when using Surface", frames1, frames2);
+        testDecode(R.raw.video_1280x720_mp4_hevc_1150kbps_30fps_aac_stereo_128kbps_48000hz, 300);
     }
 
     public void testCodecBasicH263() throws Exception {
-        Surface s = getActivity().getSurfaceHolder().getSurface();
-        int frames1 = countFrames(
-                R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz,
-                RESET_MODE_NONE, -1 /* eosframe */, s);
-        assertEquals("wrong number of frames decoded", 122, frames1);
-
-        int frames2 = countFrames(
-                R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz,
-                RESET_MODE_NONE, -1 /* eosframe */, null);
-        assertEquals("different number of frames when using Surface", frames1, frames2);
+        testDecode(R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz, 122);
     }
 
     public void testCodecBasicMpeg4() throws Exception {
-        Surface s = getActivity().getSurfaceHolder().getSurface();
-        int frames1 = countFrames(
-                R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz,
-                RESET_MODE_NONE, -1 /* eosframe */, s);
-        assertEquals("wrong number of frames decoded", 249, frames1);
-
-        int frames2 = countFrames(
-                R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz,
-                RESET_MODE_NONE, -1 /* eosframe */, null);
-        assertEquals("different number of frames when using Surface", frames1, frames2);
+        testDecode(R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz, 249);
     }
 
     public void testCodecBasicVP8() throws Exception {
-        Surface s = getActivity().getSurfaceHolder().getSurface();
-        int frames1 = countFrames(
-                R.raw.video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz,
-                RESET_MODE_NONE, -1 /* eosframe */, s);
-        assertEquals("wrong number of frames decoded", 240, frames1);
-
-        int frames2 = countFrames(
-                R.raw.video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz,
-                RESET_MODE_NONE, -1 /* eosframe */, null);
-        assertEquals("different number of frames when using Surface", frames1, frames2);
+        testDecode(R.raw.video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_48000hz, 240);
     }
 
     public void testCodecBasicVP9() throws Exception {
-        Surface s = getActivity().getSurfaceHolder().getSurface();
-        int frames1 = countFrames(
-                R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_44100hz,
-                RESET_MODE_NONE, -1 /* eosframe */, s);
-        assertEquals("wrong number of frames decoded", 240, frames1);
+        testDecode(R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz, 240);
+    }
 
-        int frames2 = countFrames(
-                R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_44100hz,
-                RESET_MODE_NONE, -1 /* eosframe */, null);
-        assertEquals("different number of frames when using Surface", frames1, frames2);
+    public void testH264Decode320x240() throws Exception {
+        testDecode(R.raw.video_320x240_mp4_h264_800kbps_30fps_aac_stereo_128kbps_44100hz, 299);
+    }
+
+    public void testH264Decode720x480() throws Exception {
+        testDecode(R.raw.video_720x480_mp4_h264_2048kbps_30fps_aac_stereo_128kbps_44100hz, 299);
+    }
+
+    public void testH264Decode30fps1280x720Tv() throws Exception {
+        if (checkTv()) {
+            assertTrue(MediaUtils.canDecodeVideo(MediaFormat.MIMETYPE_VIDEO_AVC, 1280, 720, 30));
+        }
+    }
+
+    public void testH264Decode30fps1280x720() throws Exception {
+        testDecode(R.raw.video_1280x720_mp4_h264_8192kbps_30fps_aac_stereo_128kbps_44100hz, 299);
+    }
+
+    public void testH264Decode60fps1280x720Tv() throws Exception {
+        if (checkTv()) {
+            assertTrue(MediaUtils.canDecodeVideo(MediaFormat.MIMETYPE_VIDEO_AVC, 1280, 720, 60));
+        }
+    }
+
+    public void testH264Decode60fps1280x720() throws Exception {
+        testDecode(R.raw.video_1280x720_mp4_h264_8192kbps_60fps_aac_stereo_128kbps_44100hz, 596);
+    }
+
+    public void testH264Decode30fps1920x1080Tv() throws Exception {
+        if (checkTv()) {
+            assertTrue(MediaUtils.canDecodeVideo(MediaFormat.MIMETYPE_VIDEO_AVC, 1920, 1080, 30));
+        }
+    }
+
+    public void testH264Decode30fps1920x1080() throws Exception {
+        testDecode(R.raw.video_1920x1080_mp4_h264_20480kbps_30fps_aac_stereo_128kbps_44100hz, 299);
+    }
+
+    public void testH264Decode60fps1920x1080Tv() throws Exception {
+        if (checkTv()) {
+            assertTrue(MediaUtils.canDecodeVideo(MediaFormat.MIMETYPE_VIDEO_AVC, 1920, 1080, 60));
+        }
+    }
+
+    public void testH264Decode60fps1920x1080() throws Exception {
+        testDecode(R.raw.video_1920x1080_mp4_h264_20480kbps_60fps_aac_stereo_128kbps_44100hz, 596);
+    }
+
+    public void testVP8Decode320x240() throws Exception {
+        testDecode(R.raw.video_320x240_webm_vp8_800kbps_30fps_vorbis_stereo_128kbps_48000hz, 249);
+    }
+
+    public void testVP8Decode640x360() throws Exception {
+        testDecode(R.raw.video_640x360_webm_vp8_2048kbps_30fps_vorbis_stereo_128kbps_48000hz, 249);
+    }
+
+    public void testVP8Decode30fps1280x720Tv() throws Exception {
+        if (checkTv()) {
+            assertTrue(MediaUtils.canDecodeVideo(MediaFormat.MIMETYPE_VIDEO_VP8, 1280, 720, 30));
+        }
+    }
+
+    public void testVP8Decode30fps1280x720() throws Exception {
+        testDecode(R.raw.video_1280x720_webm_vp8_8192kbps_30fps_vorbis_stereo_128kbps_48000hz, 249);
+    }
+
+    public void testVP8Decode60fps1280x720Tv() throws Exception {
+        if (checkTv()) {
+            assertTrue(MediaUtils.canDecodeVideo(MediaFormat.MIMETYPE_VIDEO_VP8, 1280, 720, 60));
+        }
+    }
+
+    public void testVP8Decode60fps1280x720() throws Exception {
+        testDecode(R.raw.video_1280x720_webm_vp8_8192kbps_60fps_vorbis_stereo_128kbps_48000hz, 249);
+    }
+
+    public void testVP8Decode30fps1920x1080Tv() throws Exception {
+        if (checkTv()) {
+            assertTrue(MediaUtils.canDecodeVideo(MediaFormat.MIMETYPE_VIDEO_VP8, 1920, 1080, 30));
+        }
+    }
+
+    public void testVP8Decode30fps1920x1080() throws Exception {
+        testDecode(R.raw.video_1920x1080_webm_vp8_20480kbps_30fps_vorbis_stereo_128kbps_48000hz,
+                249);
+    }
+
+    public void testVP8Decode60fps1920x1080Tv() throws Exception {
+        if (checkTv()) {
+            assertTrue(MediaUtils.canDecodeVideo(MediaFormat.MIMETYPE_VIDEO_VP8, 1920, 1080, 60));
+        }
+    }
+
+    public void testVP8Decode60fps1920x1080() throws Exception {
+        testDecode(R.raw.video_1920x1080_webm_vp8_20480kbps_60fps_vorbis_stereo_128kbps_48000hz,
+                249);
+    }
+
+    public void testVP9Decode320x240() throws Exception {
+        testDecode(R.raw.video_320x240_webm_vp9_600kbps_30fps_vorbis_stereo_128kbps_48000hz, 249);
+    }
+
+    public void testVP9Decode640x360() throws Exception {
+        testDecode(R.raw.video_640x360_webm_vp9_1600kbps_30fps_vorbis_stereo_128kbps_48000hz, 249);
+    }
+
+    public void testVP9Decode30fps1280x720Tv() throws Exception {
+        if (checkTv()) {
+            assertTrue(MediaUtils.canDecodeVideo(MediaFormat.MIMETYPE_VIDEO_VP9, 1280, 720, 30));
+        }
+    }
+
+    public void testVP9Decode30fps1280x720() throws Exception {
+        testDecode(R.raw.video_1280x720_webm_vp9_4096kbps_30fps_vorbis_stereo_128kbps_48000hz, 249);
+    }
+
+    public void testVP9Decode30fps1920x1080() throws Exception {
+        testDecode(R.raw.video_1920x1080_webm_vp9_10240kbps_30fps_vorbis_stereo_128kbps_48000hz,
+                249);
+    }
+
+    public void testVP9Decode30fps3840x2160() throws Exception {
+        testDecode(R.raw.video_3840x2160_webm_vp9_20480kbps_30fps_vorbis_stereo_128kbps_48000hz,
+                249);
+    }
+
+    public void testHEVCDecode352x288() throws Exception {
+        testDecode(R.raw.video_352x288_mp4_hevc_600kbps_30fps_aac_stereo_128kbps_44100hz, 299);
+    }
+
+    public void testHEVCDecode720x480() throws Exception {
+        testDecode(R.raw.video_720x480_mp4_hevc_1638kbps_30fps_aac_stereo_128kbps_44100hz, 299);
+    }
+
+    public void testHEVCDecode30fps1280x720Tv() throws Exception {
+        if (checkTv()) {
+            assertTrue(MediaUtils.canDecodeVideo(MediaFormat.MIMETYPE_VIDEO_HEVC, 1280, 720, 30));
+        }
+    }
+
+    public void testHEVCDecode30fps1280x720() throws Exception {
+        testDecode(R.raw.video_1280x720_mp4_hevc_4096kbps_30fps_aac_stereo_128kbps_44100hz, 299);
+    }
+
+    public void testHEVCDecode30fps1920x1080Tv() throws Exception {
+        if (checkTv()) {
+            assertTrue(MediaUtils.canDecodeVideo(MediaFormat.MIMETYPE_VIDEO_HEVC, 1920, 1080, 30));
+        }
+    }
+
+    public void testHEVCDecode30fps1920x1080() throws Exception {
+        testDecode(R.raw.video_1920x1080_mp4_hevc_10240kbps_30fps_aac_stereo_128kbps_44100hz, 299);
+    }
+
+    public void testHEVCDecode30fps3840x2160() throws Exception {
+        testDecode(R.raw.video_3840x2160_mp4_hevc_20480kbps_30fps_aac_stereo_128kbps_44100hz, 299);
+    }
+
+    private void testCodecEarlyEOS(int resid, int eosFrame) throws Exception {
+        if (!MediaUtils.checkCodecForResource(mContext, resid, 0 /* track */)) {
+            return; // skip
+        }
+        Surface s = getActivity().getSurfaceHolder().getSurface();
+        int frames1 = countFrames(resid, RESET_MODE_NONE, eosFrame, s);
+        assertEquals("wrong number of frames decoded", eosFrame, frames1);
     }
 
     public void testCodecEarlyEOSH263() throws Exception {
-        Surface s = getActivity().getSurfaceHolder().getSurface();
-        int frames1 = countFrames(
+        testCodecEarlyEOS(
                 R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz,
-                RESET_MODE_NONE, 64 /* eosframe */, s);
-        assertEquals("wrong number of frames decoded", 64, frames1);
+                64 /* eosframe */);
     }
 
     public void testCodecEarlyEOSH264() throws Exception {
-        Surface s = getActivity().getSurfaceHolder().getSurface();
-        int frames1 = countFrames(
+        testCodecEarlyEOS(
                 R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz,
-                RESET_MODE_NONE, 120 /* eosframe */, s);
-        assertEquals("wrong number of frames decoded", 120, frames1);
+                120 /* eosframe */);
     }
 
     public void testCodecEarlyEOSHEVC() throws Exception {
-        Surface s = getActivity().getSurfaceHolder().getSurface();
-        int frames1 = countFrames(
+        testCodecEarlyEOS(
                 R.raw.video_1280x720_mp4_hevc_1150kbps_30fps_aac_stereo_128kbps_48000hz,
-                RESET_MODE_NONE, 120 /* eosframe */, s);
-        assertEquals("wrong number of frames decoded", 120, frames1);
+                120 /* eosframe */);
     }
 
     public void testCodecEarlyEOSMpeg4() throws Exception {
-        Surface s = getActivity().getSurfaceHolder().getSurface();
-        int frames1 = countFrames(
+        testCodecEarlyEOS(
                 R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz,
-                RESET_MODE_NONE, 120 /* eosframe */, s);
-        assertEquals("wrong number of frames decoded", 120, frames1);
+                120 /* eosframe */);
     }
 
     public void testCodecEarlyEOSVP8() throws Exception {
-        Surface s = getActivity().getSurfaceHolder().getSurface();
-        int frames1 = countFrames(
-                R.raw.video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz,
-                RESET_MODE_NONE, 120 /* eosframe */, s);
-        assertEquals("wrong number of frames decoded", 120, frames1);
+        testCodecEarlyEOS(
+                R.raw.video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_48000hz,
+                120 /* eosframe */);
     }
 
     public void testCodecEarlyEOSVP9() throws Exception {
-        Surface s = getActivity().getSurfaceHolder().getSurface();
-        int frames1 = countFrames(
-                R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_44100hz,
-                RESET_MODE_NONE, 120 /* eosframe */, s);
-        assertEquals("wrong number of frames decoded", 120, frames1);
+        testCodecEarlyEOS(
+                R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz,
+                120 /* eosframe */);
     }
 
     public void testCodecResetsH264WithoutSurface() throws Exception {
@@ -1011,24 +1142,24 @@
 
     public void testCodecResetsVP8WithoutSurface() throws Exception {
         testCodecResets(
-                R.raw.video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz, null);
+                R.raw.video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_48000hz, null);
     }
 
     public void testCodecResetsVP8WithSurface() throws Exception {
         Surface s = getActivity().getSurfaceHolder().getSurface();
         testCodecResets(
-                R.raw.video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz, s);
+                R.raw.video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_48000hz, s);
     }
 
     public void testCodecResetsVP9WithoutSurface() throws Exception {
         testCodecResets(
-                R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_44100hz, null);
+                R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz, null);
     }
 
     public void testCodecResetsVP9WithSurface() throws Exception {
         Surface s = getActivity().getSurfaceHolder().getSurface();
         testCodecResets(
-                R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_44100hz, s);
+                R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz, s);
     }
 
 //    public void testCodecResetsOgg() throws Exception {
@@ -1054,6 +1185,10 @@
     }
 
     private void testCodecResets(int video, Surface s) throws Exception {
+        if (!MediaUtils.checkCodecForResource(mContext, video, 0 /* track */)) {
+            return; // skip
+        }
+
         int frames1 = countFrames(video, RESET_MODE_NONE, -1 /* eosframe */, s);
         int frames2 = countFrames(video, RESET_MODE_RECONFIGURE, -1 /* eosframe */, s);
         int frames3 = countFrames(video, RESET_MODE_FLUSH, -1 /* eosframe */, s);
@@ -1132,6 +1267,10 @@
         extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
                 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);
@@ -1436,13 +1575,13 @@
 
     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_44100hz,
+        testEOSBehavior(R.raw.video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_48000hz,
                 new int[] {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_44100hz,
+        testEOSBehavior(R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz,
                 new int[] {44, 45, 55, 43});
     }
 
diff --git a/tests/tests/media/src/android/media/cts/EncodeDecodeTest.java b/tests/tests/media/src/android/media/cts/EncodeDecodeTest.java
index a480d97..0796515 100644
--- a/tests/tests/media/src/android/media/cts/EncodeDecodeTest.java
+++ b/tests/tests/media/src/android/media/cts/EncodeDecodeTest.java
@@ -51,8 +51,9 @@
     private static final String DEBUG_FILE_NAME_BASE = "/sdcard/test.";
 
     // parameters for the encoder
-    private static final String MIME_TYPE_AVC = "video/avc";    // H.264 Advanced Video Coding
-    private static final String MIME_TYPE_VP8 = "video/x-vnd.on2.vp8";
+                                                            // H.264 Advanced Video Coding
+    private static final String MIME_TYPE_AVC = MediaFormat.MIMETYPE_VIDEO_AVC;
+    private static final String MIME_TYPE_VP8 = MediaFormat.MIMETYPE_VIDEO_VP8;
     private static final int FRAME_RATE = 15;               // 15fps
     private static final int IFRAME_INTERVAL = 10;          // 10 seconds between I-frames
 
@@ -297,20 +298,24 @@
         mLargestColorDelta = -1;
 
         try {
-            MediaCodecInfo codecInfo = selectCodec(mMimeType);
-            if (codecInfo == null) {
-                // Don't fail CTS if they don't have an AVC codec (not here, anyway).
-                Log.e(TAG, "Unable to find an appropriate codec for " + mMimeType);
-                return;
-            }
-            if (VERBOSE) Log.d(TAG, "found codec: " + codecInfo.getName());
-
-            int colorFormat = selectColorFormat(codecInfo, mMimeType);
-            if (VERBOSE) Log.d(TAG, "found colorFormat: " + colorFormat);
-
             // We avoid the device-specific limitations on width and height by using values that
             // are multiples of 16, which all tested devices seem to be able to handle.
             MediaFormat format = MediaFormat.createVideoFormat(mMimeType, mWidth, mHeight);
+            MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+            String codec = mcl.findEncoderForFormat(format);
+            if (codec == null) {
+                // Don't fail CTS if they don't have an AVC codec (not here, anyway).
+                Log.e(TAG, "Unable to find an appropriate codec for " + format);
+                return;
+            }
+            if (VERBOSE) Log.d(TAG, "found codec: " + codec);
+
+            // Create a MediaCodec for the desired codec, then configure it as an encoder with
+            // our desired properties.
+            encoder = MediaCodec.createByCodecName(codec);
+
+            int colorFormat = selectColorFormat(encoder.getCodecInfo(), mMimeType);
+            if (VERBOSE) Log.d(TAG, "found colorFormat: " + colorFormat);
 
             // Set some properties.  Failing to specify some of these can cause the MediaCodec
             // configure() call to throw an unhelpful exception.
@@ -320,9 +325,6 @@
             format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
             if (VERBOSE) Log.d(TAG, "format: " + format);
 
-            // Create a MediaCodec for the desired codec, then configure it as an encoder with
-            // our desired properties.
-            encoder = MediaCodec.createByCodecName(codecInfo.getName());
             encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
             encoder.start();
 
@@ -362,19 +364,19 @@
         mLargestColorDelta = -1;
 
         try {
-            MediaCodecInfo codecInfo = selectCodec(mMimeType);
-            if (codecInfo == null) {
-                // Don't fail CTS if they don't have an AVC codec (not here, anyway).
-                Log.e(TAG, "Unable to find an appropriate codec for " + mMimeType);
-                return;
-            }
-            if (VERBOSE) Log.d(TAG, "found codec: " + codecInfo.getName());
-
-            int colorFormat = MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface;
-
             // We avoid the device-specific limitations on width and height by using values that
             // are multiples of 16, which all tested devices seem to be able to handle.
             MediaFormat format = MediaFormat.createVideoFormat(mMimeType, mWidth, mHeight);
+            MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+            String codec = mcl.findEncoderForFormat(format);
+            if (codec == null) {
+                // Don't fail CTS if they don't have an AVC codec (not here, anyway).
+                Log.e(TAG, "Unable to find an appropriate codec for " + format);
+                return;
+            }
+            if (VERBOSE) Log.d(TAG, "found codec: " + codec);
+
+            int colorFormat = MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface;
 
             // Set some properties.  Failing to specify some of these can cause the MediaCodec
             // configure() call to throw an unhelpful exception.
@@ -396,7 +398,7 @@
 
             // Create a MediaCodec for the desired codec, then configure it as an encoder with
             // our desired properties.  Request a Surface to use for input.
-            encoder = MediaCodec.createByCodecName(codecInfo.getName());
+            encoder = MediaCodec.createByCodecName(codec);
             encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
             inputSurface = new InputSurface(encoder.createInputSurface());
             encoder.start();
@@ -424,29 +426,6 @@
     }
 
     /**
-     * Returns the first codec capable of encoding the specified MIME type, or null if no
-     * match was found.
-     */
-    private static MediaCodecInfo selectCodec(String mimeType) {
-        int numCodecs = MediaCodecList.getCodecCount();
-        for (int i = 0; i < numCodecs; i++) {
-            MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
-
-            if (!codecInfo.isEncoder()) {
-                continue;
-            }
-
-            String[] types = codecInfo.getSupportedTypes();
-            for (int j = 0; j < types.length; j++) {
-                if (types[j].equalsIgnoreCase(mimeType)) {
-                    return codecInfo;
-                }
-            }
-        }
-        return null;
-    }
-
-    /**
      * Returns a color format that is supported by the codec and by this test code.  If no
      * match is found, this throws a test failure -- the set of formats known to the test
      * should be expanded for new platforms.
diff --git a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayTest.java b/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayTest.java
index 89b06dc..d65bbf6 100755
--- a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayTest.java
+++ b/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayTest.java
@@ -67,26 +67,27 @@
 
     // Encoder parameters table, sort by encoder level from high to low.
     private static final int[][] ENCODER_PARAM_TABLE = {
-        // encoder level,                             width,   height,  bitrate,    framerate
-        {MediaCodecInfo.CodecProfileLevel.AVCLevel31, 1280,     720,    14000000,   30},
-        {MediaCodecInfo.CodecProfileLevel.AVCLevel3,   720,     480,    10000000,   30},
-        {MediaCodecInfo.CodecProfileLevel.AVCLevel22,  720,     480,    4000000,    15},
-        {MediaCodecInfo.CodecProfileLevel.AVCLevel21,  352,     576,    4000000,    25},
+        // width,  height,  bitrate,    framerate  /* level */
+        { 1280,     720,    14000000,   30 },  /* AVCLevel31 */
+        {  720,     480,    10000000,   30 },  /* AVCLevel3  */
+        {  720,     480,    4000000,    15 },  /* AVCLevel22 */
+        {  352,     576,    4000000,    25 },  /* AVCLevel21 */
     };
 
     // Virtual display characteristics.  Scaled down from full display size because not all
     // devices can encode at the resolution of their own display.
     private static final String NAME = TAG;
-    private static int sWidth = ENCODER_PARAM_TABLE[ENCODER_PARAM_TABLE.length-1][1];
-    private static int sHeight = ENCODER_PARAM_TABLE[ENCODER_PARAM_TABLE.length-1][2];
+    private static int sWidth = ENCODER_PARAM_TABLE[ENCODER_PARAM_TABLE.length-1][0];
+    private static int sHeight = ENCODER_PARAM_TABLE[ENCODER_PARAM_TABLE.length-1][1];
     private static final int DENSITY = DisplayMetrics.DENSITY_HIGH;
     private static final int UI_TIMEOUT_MS = 2000;
     private static final int UI_RENDER_PAUSE_MS = 400;
 
     // Encoder parameters.  We use the same width/height as the virtual display.
-    private static final String MIME_TYPE = "video/avc";
+    private static final String MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_AVC;
     private static int sFrameRate = 15;               // 15fps
-    private static final int IFRAME_INTERVAL = 10;    // 10 seconds between I-frames
+    // 100 days between I-frames
+    private static final int IFRAME_INTERVAL = 60 * 60 * 24 * 100;
     private static int sBitRate = 6000000;            // 6Mbps
 
     // Colors to test (RGB).  These must convert cleanly to and from BT.601 YUV.
@@ -161,56 +162,16 @@
         }
     }
 
-    private static boolean hasCodec(String mimeType) {
-        int numCodecs = MediaCodecList.getCodecCount();
-        for (int i = 0; i < numCodecs; i++) {
-            MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
-
-            if (!codecInfo.isEncoder()) {
-                continue;
-            }
-
-            String[] types = codecInfo.getSupportedTypes();
-            for (int j = 0; j < types.length; j++) {
-                if (types[j].equalsIgnoreCase(mimeType)) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
     /**
      * Returns true if the encoder level, specified in the ENCODER_PARAM_TABLE, can be supported.
      */
-    private static boolean verifySupportForEncoderLevel(int index) {
-        int numCodecs = MediaCodecList.getCodecCount();
-        for (int i = 0; i < numCodecs; i++) {
-            MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
-
-            if (!codecInfo.isEncoder()) {
-                continue;
-            }
-
-            String[] types = codecInfo.getSupportedTypes();
-            for (int j = 0; j < types.length; j++) {
-
-                if (false == types[j].equalsIgnoreCase(MIME_TYPE)) {
-                    continue;
-                }
-
-                MediaCodecInfo.CodecCapabilities caps = codecInfo.getCapabilitiesForType(types[j]);
-                for (int k = 0; k < caps.profileLevels.length; k++) {
-                    int profile = caps.profileLevels[k].profile;
-                    int level = caps.profileLevels[k].level;
-                    //Log.d(TAG, "[" + k + "] supported profile = " + profile + ", level = " + level);
-                    if (caps.profileLevels[k].level >= ENCODER_PARAM_TABLE[index][0]) {
-                        return true;
-                    }
-                }
-            }
-        }
-        return false;
+    private static boolean verifySupportForEncoderLevel(int i) {
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        MediaFormat format = MediaFormat.createVideoFormat(
+                MIME_TYPE, ENCODER_PARAM_TABLE[i][0], ENCODER_PARAM_TABLE[i][1]);
+        format.setInteger(MediaFormat.KEY_BIT_RATE, ENCODER_PARAM_TABLE[i][2]);
+        format.setInteger(MediaFormat.KEY_FRAME_RATE, ENCODER_PARAM_TABLE[i][3]);
+        return mcl.findEncoderForFormat(format) != null;
     }
 
     /**
@@ -224,10 +185,10 @@
             // Check if we can support it?
             if (verifySupportForEncoderLevel(i)) {
 
-                sWidth = ENCODER_PARAM_TABLE[i][1];
-                sHeight = ENCODER_PARAM_TABLE[i][2];
-                sBitRate = ENCODER_PARAM_TABLE[i][3];
-                sFrameRate = ENCODER_PARAM_TABLE[i][4];
+                sWidth = ENCODER_PARAM_TABLE[i][0];
+                sHeight = ENCODER_PARAM_TABLE[i][1];
+                sBitRate = ENCODER_PARAM_TABLE[i][2];
+                sFrameRate = ENCODER_PARAM_TABLE[i][3];
 
                 Log.d(TAG, "encoder parameters changed: width = " + sWidth + ", height = " + sHeight
                     + ", bitrate = " + sBitRate + ", framerate = " + sFrameRate);
@@ -245,11 +206,6 @@
         OutputSurface outputSurface = null;
         VirtualDisplay virtualDisplay = null;
 
-        // Don't run the test of the codec isn't present.
-        if (!hasCodec(MIME_TYPE)) {
-            return;
-        }
-
         try {
             // Encoded video resolution matches virtual display.
             MediaFormat encoderFormat = MediaFormat.createVideoFormat(MIME_TYPE, sWidth, sHeight);
@@ -259,7 +215,15 @@
             encoderFormat.setInteger(MediaFormat.KEY_FRAME_RATE, sFrameRate);
             encoderFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
 
-            encoder = MediaCodec.createEncoderByType(MIME_TYPE);
+            MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+            String codec = mcl.findEncoderForFormat(encoderFormat);
+            if (codec == null) {
+                // Don't run the test if the codec isn't present.
+                Log.i(TAG, "SKIPPING test: no support for " + encoderFormat);
+                return;
+            }
+
+            encoder = MediaCodec.createByCodecName(codec);
             encoder.configure(encoderFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
             Surface inputSurface = encoder.createInputSurface();
             encoder.start();
diff --git a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTest.java b/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTest.java
index 7b21997..89d6efa 100644
--- a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTest.java
+++ b/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTest.java
@@ -46,7 +46,7 @@
 import android.test.AndroidTestCase;
 import android.util.AttributeSet;
 import android.util.Log;
-import android.util.Pair;
+import android.util.Size;
 import android.view.Display;
 import android.view.Surface;
 import android.view.TextureView;
@@ -79,7 +79,7 @@
 public class EncodeVirtualDisplayWithCompositionTest extends AndroidTestCase {
     private static final String TAG = "EncodeVirtualDisplayWithCompositionTest";
     private static final boolean DBG = true;
-    private static final String MIME_TYPE = "video/avc";
+    private static final String MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_AVC;
 
     private static final long DEFAULT_WAIT_TIMEOUT_MS = 3000;
     private static final long DEFAULT_WAIT_TIMEOUT_US = 3000000;
@@ -92,6 +92,8 @@
     private static final int BITRATE_1080p = 20000000;
     private static final int BITRATE_720p = 14000000;
     private static final int BITRATE_800x480 = 14000000;
+    private static final int BITRATE_DEFAULT = 10000000;
+
     private static final int IFRAME_INTERVAL = 10;
 
     private static final int MAX_NUM_WINDOWS = 3;
@@ -138,73 +140,59 @@
 
     public void testRendering800x480Locally() throws Throwable {
         Log.i(TAG, "testRendering800x480Locally");
-        Pair<Integer, Integer> maxRes = checkMaxConcurrentEncodingDecodingResolution();
-        if (maxRes == null) {
-            fail("codec not supported");
-        }
-        if (maxRes.first >= 800 && maxRes.second >= 480) {
+        if (isConcurrentEncodingDecodingSupported(800, 480, BITRATE_800x480)) {
             runTestRenderingInSeparateThread(800, 480, false, false);
         } else {
-            Log.w(TAG, "This H/W does not support 800x480");
+            Log.i(TAG, "SKIPPING testRendering800x480Locally(): codec not supported");
         }
     }
 
     public void testRenderingMaxResolutionLocally() throws Throwable {
         Log.i(TAG, "testRenderingMaxResolutionLocally");
-        Pair<Integer, Integer> maxRes = checkMaxConcurrentEncodingDecodingResolution();
+        Size maxRes = checkMaxConcurrentEncodingDecodingResolution();
         if (maxRes == null) {
-            fail("codec not supported");
+            Log.i(TAG, "SKIPPING testRenderingMaxResolutionLocally(): codec not supported");
+        } else {
+            Log.w(TAG, "Trying resolution " + maxRes);
+            runTestRenderingInSeparateThread(maxRes.getWidth(), maxRes.getHeight(), false, false);
         }
-        Log.w(TAG, "Trying resolution w:" + maxRes.first + " h:" + maxRes.second);
-        runTestRenderingInSeparateThread(maxRes.first, maxRes.second, false, false);
     }
 
     public void testRendering800x480Remotely() throws Throwable {
         Log.i(TAG, "testRendering800x480Remotely");
-        Pair<Integer, Integer> maxRes = checkMaxConcurrentEncodingDecodingResolution();
-        if (maxRes == null) {
-            fail("codec not supported");
-        }
-        if (maxRes.first >= 800 && maxRes.second >= 480) {
+        if (isConcurrentEncodingDecodingSupported(800, 480, BITRATE_800x480)) {
             runTestRenderingInSeparateThread(800, 480, true, false);
         } else {
-            Log.w(TAG, "This H/W does not support 800x480");
+            Log.i(TAG, "SKIPPING testRendering800x480Remotely(): codec not supported");
         }
     }
 
     public void testRenderingMaxResolutionRemotely() throws Throwable {
         Log.i(TAG, "testRenderingMaxResolutionRemotely");
-        Pair<Integer, Integer> maxRes = checkMaxConcurrentEncodingDecodingResolution();
+        Size maxRes = checkMaxConcurrentEncodingDecodingResolution();
         if (maxRes == null) {
-            fail("codec not supported");
+            Log.i(TAG, "SKIPPING testRenderingMaxResolutionRemotely(): codec not supported");
+        } else {
+            Log.w(TAG, "Trying resolution " + maxRes);
+            runTestRenderingInSeparateThread(maxRes.getWidth(), maxRes.getHeight(), true, false);
         }
-        Log.w(TAG, "Trying resolution w:" + maxRes.first + " h:" + maxRes.second);
-        runTestRenderingInSeparateThread(maxRes.first, maxRes.second, true, false);
     }
 
     public void testRendering800x480RemotelyWith3Windows() throws Throwable {
         Log.i(TAG, "testRendering800x480RemotelyWith3Windows");
-        Pair<Integer, Integer> maxRes = checkMaxConcurrentEncodingDecodingResolution();
-        if (maxRes == null) {
-            fail("codec not supported");
-        }
-        if (maxRes.first >= 800 && maxRes.second >= 480) {
+        if (isConcurrentEncodingDecodingSupported(800, 480, BITRATE_800x480)) {
             runTestRenderingInSeparateThread(800, 480, true, true);
         } else {
-            Log.w(TAG, "This H/W does not support 800x480");
+            Log.i(TAG, "SKIPPING testRendering800x480RemotelyWith3Windows(): codec not supported");
         }
     }
 
     public void testRendering800x480LocallyWith3Windows() throws Throwable {
         Log.i(TAG, "testRendering800x480LocallyWith3Windows");
-        Pair<Integer, Integer> maxRes = checkMaxConcurrentEncodingDecodingResolution();
-        if (maxRes == null) {
-            fail("codec not supported");
-        }
-        if (maxRes.first >= 800 && maxRes.second >= 480) {
+        if (isConcurrentEncodingDecodingSupported(800, 480, BITRATE_800x480)) {
             runTestRenderingInSeparateThread(800, 480, false, true);
         } else {
-            Log.w(TAG, "This H/W does not support 800x480");
+            Log.i(TAG, "SKIPPING testRendering800x480LocallyWith3Windows(): codec not supported");
         }
     }
 
@@ -418,8 +406,8 @@
     private static final int NUM_DISPLAY_CREATION = 10;
     private static final int NUM_RENDERING = 10;
     private void doTestVirtualDisplayRecycles(int numDisplays) throws Exception {
-        CodecInfo codecInfo = getAvcSupportedFormatInfo();
-        if (codecInfo == null) {
+        Size maxSize = getMaxSupportedEncoderSize();
+        if (maxSize == null) {
             Log.i(TAG, "no codec found, skipping");
             return;
         }
@@ -431,13 +419,13 @@
                 Log.i(TAG, "start encoding");
             }
             EncodingHelper encodingHelper = new EncodingHelper();
-            mEncodingSurface = encodingHelper.startEncoding(codecInfo.mMaxW, codecInfo.mMaxH,
+            mEncodingSurface = encodingHelper.startEncoding(maxSize.getWidth(), maxSize.getHeight(),
                     mEncoderEventListener);
             GlCompositor compositor = new GlCompositor();
             if (DBG) {
                 Log.i(TAG, "start composition");
             }
-            compositor.startComposition(mEncodingSurface, codecInfo.mMaxW, codecInfo.mMaxH,
+            compositor.startComposition(mEncodingSurface, maxSize.getWidth(), maxSize.getHeight(),
                     numDisplays);
             for (int j = 0; j < NUM_DISPLAY_CREATION; j++) {
                 if (DBG) {
@@ -447,7 +435,7 @@
                     virtualDisplays[k] =
                         new VirtualDisplayPresentation(getContext(),
                                 compositor.getWindowSurface(k),
-                                codecInfo.mMaxW/numDisplays, codecInfo.mMaxH);
+                                maxSize.getWidth()/numDisplays, maxSize.getHeight());
                     virtualDisplays[k].createVirtualDisplay();
                     virtualDisplays[k].createPresentation();
                 }
@@ -543,7 +531,7 @@
             MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mW, mH);
             format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
                     MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
-            int bitRate = 10000000;
+            int bitRate = BITRATE_DEFAULT;
             if (mW == 1920 && mH == 1080) {
                 bitRate = BITRATE_1080p;
             } else if (mW == 1280 && mH == 720) {
@@ -1329,152 +1317,56 @@
         }
     }
 
-    private static class CodecInfo {
-        public int mMaxW;
-        public int mMaxH;
-        public int mFps;
-        public int mBitRate;
-        public String mCodecName;
-    };
+    private static Size getMaxSupportedEncoderSize() {
+        final Size[] standardSizes = new Size[] {
+            new Size(1920, 1080),
+            new Size(1280, 720),
+            new Size(720, 480),
+            new Size(352, 576)
+        };
 
-    /**
-     * Returns the first codec capable of encoding the specified MIME type, or null if no
-     * match was found.
-     */
-    private static MediaCodecInfo selectCodec(String mimeType) {
-        int numCodecs = MediaCodecList.getCodecCount();
-        for (int i = 0; i < numCodecs; i++) {
-            MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
-
-            if (!codecInfo.isEncoder()) {
-                continue;
-            }
-
-            String[] types = codecInfo.getSupportedTypes();
-            for (int j = 0; j < types.length; j++) {
-                if (types[j].equalsIgnoreCase(mimeType)) {
-                    return codecInfo;
-                }
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        for (Size sz : standardSizes) {
+            MediaFormat format = MediaFormat.createVideoFormat(
+                MIME_TYPE, sz.getWidth(), sz.getHeight());
+            format.setInteger(MediaFormat.KEY_FRAME_RATE, 15); // require at least 15fps
+            if (mcl.findEncoderForFormat(format) != null) {
+                return sz;
             }
         }
         return null;
     }
 
-    private static CodecInfo getAvcSupportedFormatInfo() {
-        MediaCodecInfo mediaCodecInfo = selectCodec(MIME_TYPE);
-        if (mediaCodecInfo == null) {
-            return null;
-        }
-        CodecCapabilities cap = mediaCodecInfo.getCapabilitiesForType(MIME_TYPE);
-        if (cap == null) { // not supported
-            return null;
-        }
-        CodecInfo info = new CodecInfo();
-        int highestLevel = 0;
-        for (CodecProfileLevel lvl : cap.profileLevels) {
-            if (lvl.level > highestLevel) {
-                highestLevel = lvl.level;
-            }
-        }
-        int maxW = 0;
-        int maxH = 0;
-        int bitRate = 0;
-        int fps = 0; // frame rate for the max resolution
-        switch(highestLevel) {
-            // Do not support Level 1 to 2.
-            case CodecProfileLevel.AVCLevel1:
-            case CodecProfileLevel.AVCLevel11:
-            case CodecProfileLevel.AVCLevel12:
-            case CodecProfileLevel.AVCLevel13:
-            case CodecProfileLevel.AVCLevel1b:
-            case CodecProfileLevel.AVCLevel2:
-                return null;
-            case CodecProfileLevel.AVCLevel21:
-                maxW = 352;
-                maxH = 576;
-                bitRate = 4000000;
-                fps = 25;
-                break;
-            case CodecProfileLevel.AVCLevel22:
-                maxW = 720;
-                maxH = 480;
-                bitRate = 4000000;
-                fps = 15;
-                break;
-            case CodecProfileLevel.AVCLevel3:
-                maxW = 720;
-                maxH = 480;
-                bitRate = 10000000;
-                fps = 30;
-                break;
-            case CodecProfileLevel.AVCLevel31:
-                maxW = 1280;
-                maxH = 720;
-                bitRate = 14000000;
-                fps = 30;
-                break;
-            case CodecProfileLevel.AVCLevel32:
-                maxW = 1280;
-                maxH = 720;
-                bitRate = 20000000;
-                fps = 60;
-                break;
-            case CodecProfileLevel.AVCLevel4: // only try up to 1080p
-            default:
-                maxW = 1920;
-                maxH = 1080;
-                bitRate = 20000000;
-                fps = 30;
-                break;
-        }
-        info.mMaxW = maxW;
-        info.mMaxH = maxH;
-        info.mFps = fps;
-        info.mBitRate = bitRate;
-        info.mCodecName = mediaCodecInfo.getName();
-        Log.i(TAG, "AVC Level 0x" + Integer.toHexString(highestLevel) + " bit rate " + bitRate +
-                " fps " + info.mFps + " w " + maxW + " h " + maxH);
-
-        return info;
-    }
-
     /**
      * Check maximum concurrent encoding / decoding resolution allowed.
      * Some H/Ws cannot support maximum resolution reported in encoder if decoder is running
      * at the same time.
-     * Check is done for 4 different levels: 1080p, 720p, 800x480 and max of encoder if is is
-     * smaller than 800x480.
+     * Check is done for 4 different levels: 1080p, 720p, 800x480, 480p
+     * (The last one is required by CDD.)
      */
-    private Pair<Integer, Integer> checkMaxConcurrentEncodingDecodingResolution() {
-        CodecInfo codecInfo = getAvcSupportedFormatInfo();
-        if (codecInfo == null) {
-            return null;
+    private Size checkMaxConcurrentEncodingDecodingResolution() {
+        if (isConcurrentEncodingDecodingSupported(1920, 1080, BITRATE_1080p)) {
+            return new Size(1920, 1080);
+        } else if (isConcurrentEncodingDecodingSupported(1280, 720, BITRATE_720p)) {
+            return new Size(1280, 720);
+        } else if (isConcurrentEncodingDecodingSupported(800, 480, BITRATE_800x480)) {
+            return new Size(800, 480);
+        } else if (isConcurrentEncodingDecodingSupported(720, 480, BITRATE_DEFAULT)) {
+            return new Size(720, 480);
         }
-        int maxW = codecInfo.mMaxW;
-        int maxH = codecInfo.mMaxH;
-        if (maxW >= 1920 && maxH >= 1080) {
-            if (isConcurrentEncodingDecodingSupported(1920, 1080, BITRATE_1080p)) {
-                return new Pair<Integer, Integer>(1920, 1080);
-            }
-        }
-        if (maxW >= 1280 && maxH >= 720) {
-            if (isConcurrentEncodingDecodingSupported(1280, 720, BITRATE_720p)) {
-                return new Pair<Integer, Integer>(1280, 720);
-            }
-        }
-        if (maxW >= 800 && maxH >= 480) {
-            if (isConcurrentEncodingDecodingSupported(800, 480, BITRATE_800x480)) {
-                return new Pair<Integer, Integer>(800, 480);
-            }
-        }
-        if (!isConcurrentEncodingDecodingSupported(codecInfo.mMaxW, codecInfo.mMaxH,
-                codecInfo.mBitRate)) {
-            fail("should work with advertised resolution");
-        }
-        return new Pair<Integer, Integer>(maxW, maxH);
+        Log.i(TAG, "SKIPPING test: concurrent encoding and decoding is not supported");
+        return null;
     }
 
     private boolean isConcurrentEncodingDecodingSupported(int w, int h, int bitRate) {
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        MediaFormat testFormat = MediaFormat.createVideoFormat(MIME_TYPE, w, h);
+        testFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
+        if (mcl.findDecoderForFormat(testFormat) == null
+                || mcl.findEncoderForFormat(testFormat) == null) {
+            return false;
+        }
+
         MediaCodec decoder = null;
         OutputSurface decodingSurface = null;
         MediaCodec encoder = null;
diff --git a/tests/tests/media/src/android/media/cts/EncoderTest.java b/tests/tests/media/src/android/media/cts/EncoderTest.java
index c2e59d4..4b2a168 100644
--- a/tests/tests/media/src/android/media/cts/EncoderTest.java
+++ b/tests/tests/media/src/android/media/cts/EncoderTest.java
@@ -50,14 +50,14 @@
 
         for (int j = 0; j < kBitRates.length; ++j) {
             MediaFormat format  = new MediaFormat();
-            format.setString(MediaFormat.KEY_MIME, "audio/3gpp");
+            format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_AMR_NB);
             format.setInteger(MediaFormat.KEY_SAMPLE_RATE, 8000);
             format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
             format.setInteger(MediaFormat.KEY_BIT_RATE, kBitRates[j]);
             formats.push(format);
         }
 
-        testEncoderWithFormats("audio/3gpp", formats);
+        testEncoderWithFormats(MediaFormat.MIMETYPE_AUDIO_AMR_NB, formats);
     }
 
     public void testAMRWBEncoders() {
@@ -68,14 +68,14 @@
 
         for (int j = 0; j < kBitRates.length; ++j) {
             MediaFormat format  = new MediaFormat();
-            format.setString(MediaFormat.KEY_MIME, "audio/amr-wb");
+            format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_AMR_WB);
             format.setInteger(MediaFormat.KEY_SAMPLE_RATE, 16000);
             format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
             format.setInteger(MediaFormat.KEY_BIT_RATE, kBitRates[j]);
             formats.push(format);
         }
 
-        testEncoderWithFormats("audio/amr-wb", formats);
+        testEncoderWithFormats(MediaFormat.MIMETYPE_AUDIO_AMR_WB, formats);
     }
 
     public void testAACEncoders() {
@@ -99,7 +99,7 @@
                 for (int j = 0; j < kBitRates.length; ++j) {
                     for (int ch = 1; ch <= 2; ++ch) {
                         MediaFormat format  = new MediaFormat();
-                        format.setString(MediaFormat.KEY_MIME, "audio/mp4a-latm");
+                        format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_AAC);
 
                         format.setInteger(
                                 MediaFormat.KEY_AAC_PROFILE, kAACProfiles[k]);
@@ -115,7 +115,7 @@
             }
         }
 
-        testEncoderWithFormats("audio/mp4a-latm", formats);
+        testEncoderWithFormats(MediaFormat.MIMETYPE_AUDIO_AAC, formats);
     }
 
     private void testEncoderWithFormats(
@@ -135,27 +135,13 @@
     private List<String> getEncoderNamesForType(String mime) {
         LinkedList<String> names = new LinkedList<String>();
 
-        int n = MediaCodecList.getCodecCount();
-        for (int i = 0; i < n; ++i) {
-            MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
-
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        for (MediaCodecInfo info : mcl.getCodecInfos()) {
             if (!info.isEncoder()) {
                 continue;
             }
-
-            if (!info.getName().startsWith("OMX.")) {
-                // Unfortunately for legacy reasons, "AACEncoder", a
-                // non OMX component had to be in this list for the video
-                // editor code to work... but it cannot actually be instantiated
-                // using MediaCodec.
-                Log.d(TAG, "skipping '" + info.getName() + "'.");
-                continue;
-            }
-
-            String[] supportedTypes = info.getSupportedTypes();
-
-            for (int j = 0; j < supportedTypes.length; ++j) {
-                if (supportedTypes[j].equalsIgnoreCase(mime)) {
+            for (String type : info.getSupportedTypes()) {
+                if (type.equalsIgnoreCase(mime)) {
                     names.push(info.getName());
                     break;
                 }
diff --git a/tests/tests/media/src/android/media/cts/EnvReverbTest.java b/tests/tests/media/src/android/media/cts/EnvReverbTest.java
index e2e9b6d..4cfb744 100644
--- a/tests/tests/media/src/android/media/cts/EnvReverbTest.java
+++ b/tests/tests/media/src/android/media/cts/EnvReverbTest.java
@@ -304,6 +304,9 @@
 
     //Test case 2.1: test setEnabled() throws exception after release
     public void test2_1SetEnabledAfterRelease() throws Exception {
+        if (!isEnvReverbAvailable()) {
+            return;
+        }
         getReverb(0);
         mReverb.release();
         try {
diff --git a/tests/tests/media/src/android/media/cts/ExtractDecodeEditEncodeMuxTest.java b/tests/tests/media/src/android/media/cts/ExtractDecodeEditEncodeMuxTest.java
index 43b769a..029a632 100644
--- a/tests/tests/media/src/android/media/cts/ExtractDecodeEditEncodeMuxTest.java
+++ b/tests/tests/media/src/android/media/cts/ExtractDecodeEditEncodeMuxTest.java
@@ -62,16 +62,18 @@
     private static final File OUTPUT_FILENAME_DIR = Environment.getExternalStorageDirectory();
 
     // parameters for the video encoder
-    private static final String OUTPUT_VIDEO_MIME_TYPE = "video/avc"; // H.264 Advanced Video Coding
-    private static final int OUTPUT_VIDEO_BIT_RATE = 2000000; // 2Mbps
-    private static final int OUTPUT_VIDEO_FRAME_RATE = 15; // 15fps
+                                                                // H.264 Advanced Video Coding
+    private static final String OUTPUT_VIDEO_MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_AVC;
+    private static final int OUTPUT_VIDEO_BIT_RATE = 2000000;   // 2Mbps
+    private static final int OUTPUT_VIDEO_FRAME_RATE = 15;      // 15fps
     private static final int OUTPUT_VIDEO_IFRAME_INTERVAL = 10; // 10 seconds between I-frames
     private static final int OUTPUT_VIDEO_COLOR_FORMAT =
             MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface;
 
     // parameters for the audio encoder
-    private static final String OUTPUT_AUDIO_MIME_TYPE = "audio/mp4a-latm"; // Advanced Audio Coding
-    private static final int OUTPUT_AUDIO_CHANNEL_COUNT = 2; // Must match the input stream.
+                                                                // Advanced Audio Coding
+    private static final String OUTPUT_AUDIO_MIME_TYPE = MediaFormat.MIMETYPE_AUDIO_AAC;
+    private static final int OUTPUT_AUDIO_CHANNEL_COUNT = 2;    // Must match the input stream.
     private static final int OUTPUT_AUDIO_BIT_RATE = 128 * 1024;
     private static final int OUTPUT_AUDIO_AAC_PROFILE =
             MediaCodecInfo.CodecProfileLevel.AACObjectHE;
@@ -259,21 +261,45 @@
         // Exception that may be thrown during release.
         Exception exception = null;
 
-        MediaCodecInfo videoCodecInfo = selectCodec(OUTPUT_VIDEO_MIME_TYPE);
-        if (videoCodecInfo == null) {
-            // Don't fail CTS if they don't have an AVC codec (not here, anyway).
-            Log.e(TAG, "Unable to find an appropriate codec for " + OUTPUT_VIDEO_MIME_TYPE);
-            return;
-        }
-        if (VERBOSE) Log.d(TAG, "video found codec: " + videoCodecInfo.getName());
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
 
-        MediaCodecInfo audioCodecInfo = selectCodec(OUTPUT_AUDIO_MIME_TYPE);
-        if (audioCodecInfo == null) {
-            // Don't fail CTS if they don't have an AAC codec (not here, anyway).
-            Log.e(TAG, "Unable to find an appropriate codec for " + OUTPUT_AUDIO_MIME_TYPE);
+        // We avoid the device-specific limitations on width and height by using values
+        // that are multiples of 16, which all tested devices seem to be able to handle.
+        MediaFormat outputVideoFormat =
+                MediaFormat.createVideoFormat(OUTPUT_VIDEO_MIME_TYPE, mWidth, mHeight);
+
+        // Set some properties. Failing to specify some of these can cause the MediaCodec
+        // configure() call to throw an unhelpful exception.
+        outputVideoFormat.setInteger(
+                MediaFormat.KEY_COLOR_FORMAT, OUTPUT_VIDEO_COLOR_FORMAT);
+        outputVideoFormat.setInteger(MediaFormat.KEY_BIT_RATE, OUTPUT_VIDEO_BIT_RATE);
+        outputVideoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, OUTPUT_VIDEO_FRAME_RATE);
+        outputVideoFormat.setInteger(
+                MediaFormat.KEY_I_FRAME_INTERVAL, OUTPUT_VIDEO_IFRAME_INTERVAL);
+        if (VERBOSE) Log.d(TAG, "video format: " + outputVideoFormat);
+
+        String videoEncoderName = mcl.findEncoderForFormat(outputVideoFormat);
+        if (videoEncoderName == null) {
+            // Don't fail CTS if they don't have an AVC codec (not here, anyway).
+            Log.e(TAG, "Unable to find an appropriate codec for " + outputVideoFormat);
             return;
         }
-        if (VERBOSE) Log.d(TAG, "audio found codec: " + audioCodecInfo.getName());
+        if (VERBOSE) Log.d(TAG, "video found codec: " + videoEncoderName);
+
+        MediaFormat outputAudioFormat =
+                MediaFormat.createAudioFormat(
+                        OUTPUT_AUDIO_MIME_TYPE, OUTPUT_AUDIO_SAMPLE_RATE_HZ,
+                        OUTPUT_AUDIO_CHANNEL_COUNT);
+        outputAudioFormat.setInteger(MediaFormat.KEY_BIT_RATE, OUTPUT_AUDIO_BIT_RATE);
+        outputAudioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, OUTPUT_AUDIO_AAC_PROFILE);
+
+        String audioEncoderName = mcl.findEncoderForFormat(outputAudioFormat);
+        if (audioEncoderName == null) {
+            // Don't fail CTS if they don't have an AAC codec (not here, anyway).
+            Log.e(TAG, "Unable to find an appropriate codec for " + outputAudioFormat);
+            return;
+        }
+        if (VERBOSE) Log.d(TAG, "audio found codec: " + audioEncoderName);
 
         MediaExtractor videoExtractor = null;
         MediaExtractor audioExtractor = null;
@@ -293,32 +319,17 @@
                 assertTrue("missing video track in test video", videoInputTrack != -1);
                 MediaFormat inputFormat = videoExtractor.getTrackFormat(videoInputTrack);
 
-                // We avoid the device-specific limitations on width and height by using values
-                // that are multiples of 16, which all tested devices seem to be able to handle.
-                MediaFormat outputVideoFormat =
-                        MediaFormat.createVideoFormat(OUTPUT_VIDEO_MIME_TYPE, mWidth, mHeight);
-
-                // Set some properties. Failing to specify some of these can cause the MediaCodec
-                // configure() call to throw an unhelpful exception.
-                outputVideoFormat.setInteger(
-                        MediaFormat.KEY_COLOR_FORMAT, OUTPUT_VIDEO_COLOR_FORMAT);
-                outputVideoFormat.setInteger(MediaFormat.KEY_BIT_RATE, OUTPUT_VIDEO_BIT_RATE);
-                outputVideoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, OUTPUT_VIDEO_FRAME_RATE);
-                outputVideoFormat.setInteger(
-                        MediaFormat.KEY_I_FRAME_INTERVAL, OUTPUT_VIDEO_IFRAME_INTERVAL);
-                if (VERBOSE) Log.d(TAG, "video format: " + outputVideoFormat);
-
                 // Create a MediaCodec for the desired codec, then configure it as an encoder with
                 // our desired properties. Request a Surface to use for input.
                 AtomicReference<Surface> inputSurfaceReference = new AtomicReference<Surface>();
                 videoEncoder = createVideoEncoder(
-                        videoCodecInfo, outputVideoFormat, inputSurfaceReference);
+                        videoEncoderName, outputVideoFormat, inputSurfaceReference);
                 inputSurface = new InputSurface(inputSurfaceReference.get());
                 inputSurface.makeCurrent();
                 // Create a MediaCodec for the decoder, based on the extractor's format.
                 outputSurface = new OutputSurface();
                 outputSurface.changeFragmentShader(FRAGMENT_SHADER);
-                videoDecoder = createVideoDecoder(inputFormat, outputSurface.getSurface());
+                videoDecoder = createVideoDecoder(mcl, inputFormat, outputSurface.getSurface());
             }
 
             if (mCopyAudio) {
@@ -327,18 +338,11 @@
                 assertTrue("missing audio track in test video", audioInputTrack != -1);
                 MediaFormat inputFormat = audioExtractor.getTrackFormat(audioInputTrack);
 
-                MediaFormat outputAudioFormat =
-                        MediaFormat.createAudioFormat(
-                                OUTPUT_AUDIO_MIME_TYPE, OUTPUT_AUDIO_SAMPLE_RATE_HZ,
-                                OUTPUT_AUDIO_CHANNEL_COUNT);
-                outputAudioFormat.setInteger(MediaFormat.KEY_BIT_RATE, OUTPUT_AUDIO_BIT_RATE);
-                outputAudioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, OUTPUT_AUDIO_AAC_PROFILE);
-
                 // Create a MediaCodec for the desired codec, then configure it as an encoder with
                 // our desired properties. Request a Surface to use for input.
-                audioEncoder = createAudioEncoder(audioCodecInfo, outputAudioFormat);
+                audioEncoder = createAudioEncoder(audioEncoderName, outputAudioFormat);
                 // Create a MediaCodec for the decoder, based on the extractor's format.
-                audioDecoder = createAudioDecoder(inputFormat);
+                audioDecoder = createAudioDecoder(mcl, inputFormat);
             }
 
             // Creates a muxer but do not start or add tracks just yet.
@@ -517,9 +521,9 @@
      * @param inputFormat the format of the stream to decode
      * @param surface into which to decode the frames
      */
-    private MediaCodec createVideoDecoder(MediaFormat inputFormat, Surface surface)
-            throws IOException {
-        MediaCodec decoder = MediaCodec.createDecoderByType(getMimeTypeFor(inputFormat));
+    private MediaCodec createVideoDecoder(
+            MediaCodecList mcl, MediaFormat inputFormat, Surface surface) throws IOException {
+        MediaCodec decoder = MediaCodec.createByCodecName(mcl.findDecoderForFormat(inputFormat));
         decoder.configure(inputFormat, surface, null, 0);
         decoder.start();
         return decoder;
@@ -536,11 +540,11 @@
      * @param surfaceReference to store the surface to use as input
      */
     private MediaCodec createVideoEncoder(
-            MediaCodecInfo codecInfo,
+            String codecName,
             MediaFormat format,
             AtomicReference<Surface> surfaceReference)
             throws IOException {
-        MediaCodec encoder = MediaCodec.createByCodecName(codecInfo.getName());
+        MediaCodec encoder = MediaCodec.createByCodecName(codecName);
         encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
         // Must be called before start() is.
         surfaceReference.set(encoder.createInputSurface());
@@ -553,9 +557,9 @@
      *
      * @param inputFormat the format of the stream to decode
      */
-    private MediaCodec createAudioDecoder(MediaFormat inputFormat)
-            throws IOException {
-        MediaCodec decoder = MediaCodec.createDecoderByType(getMimeTypeFor(inputFormat));
+    private MediaCodec createAudioDecoder(
+            MediaCodecList mcl, MediaFormat inputFormat) throws IOException {
+        MediaCodec decoder = MediaCodec.createByCodecName(mcl.findDecoderForFormat(inputFormat));
         decoder.configure(inputFormat, null, null, 0);
         decoder.start();
         return decoder;
@@ -567,9 +571,9 @@
      * @param codecInfo of the codec to use
      * @param format of the stream to be produced
      */
-    private MediaCodec createAudioEncoder(MediaCodecInfo codecInfo, MediaFormat format) 
+    private MediaCodec createAudioEncoder(String codecName, MediaFormat format)
             throws IOException {
-        MediaCodec encoder = MediaCodec.createByCodecName(codecInfo.getName());
+        MediaCodec encoder = MediaCodec.createByCodecName(codecName);
         encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
         encoder.start();
         return encoder;
@@ -1126,28 +1130,4 @@
     private static String getMimeTypeFor(MediaFormat format) {
         return format.getString(MediaFormat.KEY_MIME);
     }
-
-    /**
-     * Returns the first codec capable of encoding the specified MIME type, or null if no match was
-     * found.
-     */
-    private static MediaCodecInfo selectCodec(String mimeType) {
-        int numCodecs = MediaCodecList.getCodecCount();
-        for (int i = 0; i < numCodecs; i++) {
-            MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
-
-            if (!codecInfo.isEncoder()) {
-                continue;
-            }
-
-            String[] types = codecInfo.getSupportedTypes();
-            for (int j = 0; j < types.length; j++) {
-                if (types[j].equalsIgnoreCase(mimeType)) {
-                    return codecInfo;
-                }
-            }
-        }
-        return null;
-    }
-
 }
diff --git a/tests/tests/media/src/android/media/cts/ImageReaderDecoderTest.java b/tests/tests/media/src/android/media/cts/ImageReaderDecoderTest.java
index d620995..5f326ee 100644
--- a/tests/tests/media/src/android/media/cts/ImageReaderDecoderTest.java
+++ b/tests/tests/media/src/android/media/cts/ImageReaderDecoderTest.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.content.res.AssetFileDescriptor;
 import android.content.res.Resources;
+import android.cts.util.MediaUtils;
 import android.graphics.ImageFormat;
 import android.media.Image;
 import android.media.Image.Plane;
@@ -28,7 +29,6 @@
 import android.media.MediaCodec;
 import android.media.MediaCodecInfo;
 import android.media.MediaCodecInfo.CodecCapabilities;
-import android.media.MediaCodecList;
 import android.media.MediaExtractor;
 import android.media.MediaFormat;
 import android.os.Handler;
@@ -160,6 +160,10 @@
         ByteBuffer[] decoderInputBuffers;
         ByteBuffer[] decoderOutputBuffers;
 
+        if (!MediaUtils.checkCodecForResource(mContext, video, 0 /* track */)) {
+            return; // skip
+        }
+
         AssetFileDescriptor vidFD = mResources.openRawResourceFd(video);
 
         extractor = new MediaExtractor();
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java b/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
index 67c0591..979a5d1 100644
--- a/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
@@ -15,10 +15,18 @@
  */
 package android.media.cts;
 
+import android.content.pm.PackageManager;
+import android.cts.util.MediaUtils;
 import android.media.MediaCodecInfo;
 import android.media.MediaCodecInfo.CodecCapabilities;
 import android.media.MediaCodecInfo.CodecProfileLevel;
+import static android.media.MediaCodecInfo.CodecProfileLevel.*;
+import static android.media.MediaFormat.MIMETYPE_VIDEO_AVC;
+import static android.media.MediaFormat.MIMETYPE_VIDEO_H263;
+import static android.media.MediaFormat.MIMETYPE_VIDEO_HEVC;
+import static android.media.MediaFormat.MIMETYPE_VIDEO_MPEG4;
 import android.media.MediaCodecList;
+import android.media.MediaFormat;
 import android.media.MediaPlayer;
 
 import android.os.Build;
@@ -31,30 +39,117 @@
 public class MediaCodecCapabilitiesTest extends MediaPlayerTestBase {
 
     private static final String TAG = "MediaCodecCapabilitiesTest";
-    private static final String AVC_MIME = "video/avc";
-    private static final String HEVC_MIME = "video/hevc";
     private static final int PLAY_TIME_MS = 30000;
 
+    // Android device implementations with H.264 encoders, MUST support Baseline Profile Level 3.
+    // SHOULD support Main Profile/ Level 4, if supported the device must also support Main
+    // Profile/Level 4 decoding.
+    public void testH264EncoderProfileAndLevel() throws Exception {
+        if (!MediaUtils.checkEncoder(MIMETYPE_VIDEO_AVC)) {
+            return; // skip
+        }
+
+        assertTrue(
+                "H.264 must support Baseline Profile Level 3",
+                hasEncoder(MIMETYPE_VIDEO_AVC, AVCProfileBaseline, AVCLevel3));
+
+        if (hasEncoder(MIMETYPE_VIDEO_AVC, AVCProfileMain, AVCLevel4)) {
+            assertTrue(
+                    "H.264 decoder must support Main Profile Level 4 if it can encode it",
+                    hasDecoder(MIMETYPE_VIDEO_AVC, AVCProfileMain, AVCLevel4));
+        }
+    }
+
+    // Android device implementations with H.264 decoders, MUST support Baseline Profile Level 3.
+    // Android Television Devices MUST support High Profile Level 4.2.
+    public void testH264DecoderProfileAndLevel() throws Exception {
+        if (!MediaUtils.checkDecoder(MIMETYPE_VIDEO_AVC)) {
+            return; // skip
+        }
+
+        assertTrue(
+                "H.264 must support Baseline Profile Level 3",
+                hasDecoder(MIMETYPE_VIDEO_AVC, AVCProfileBaseline, AVCLevel3));
+
+        if (isTv()) {
+            assertTrue(
+                    "H.264 must support High Profile Level 4.2 on TV",
+                    checkDecoder(MIMETYPE_VIDEO_AVC, AVCProfileHigh, AVCLevel42));
+        }
+    }
+
+    // Android device implementations with H.263 encoders, MUST support Level 45.
+    public void testH263EncoderProfileAndLevel() throws Exception {
+        if (!MediaUtils.checkEncoder(MIMETYPE_VIDEO_H263)) {
+            return; // skip
+        }
+
+        assertTrue(
+                "H.263 must support Level 45",
+                hasEncoder(MIMETYPE_VIDEO_H263, MPEG4ProfileSimple, H263Level45));
+    }
+
+    // Android device implementations with H.263 decoders, MUST support Level 30.
+    public void testH263DecoderProfileAndLevel() throws Exception {
+        if (!MediaUtils.checkDecoder(MIMETYPE_VIDEO_H263)) {
+            return; // skip
+        }
+
+        assertTrue(
+                "H.263 must support Level 30",
+                hasDecoder(MIMETYPE_VIDEO_H263, MPEG4ProfileSimple, H263Level30));
+    }
+
+    // Android device implementations with MPEG-4 decoders, MUST support Simple Profile Level 3.
+    public void testMpeg4DecoderProfileAndLevel() throws Exception {
+        if (!MediaUtils.checkDecoder(MIMETYPE_VIDEO_MPEG4)) {
+            return; // skip
+        }
+
+        assertTrue(
+                "MPEG-4 must support Simple Profile Level 3",
+                hasDecoder(MIMETYPE_VIDEO_MPEG4, MPEG4ProfileSimple, MPEG4Level3));
+    }
+
+    // Android device implementations, when supporting H.265 codec MUST support the Main Profile
+    // Level 3 Main tier.
+    // Android Television Devices MUST support the Main Profile Level 4.1 Main tier.
+    // When the UHD video decoding profile is supported, it MUST support Main10 Level 5 Main
+    // Tier profile.
+    public void testH265DecoderProfileAndLevel() throws Exception {
+        if (!MediaUtils.checkDecoder(MIMETYPE_VIDEO_HEVC)) {
+            return; // skip
+        }
+
+        assertTrue(
+                "H.265 must support Main Profile Main Tier Level 3",
+                hasDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel3));
+
+        if (isTv()) {
+            assertTrue(
+                    "H.265 must support Main Profile Main Tier Level 4.1 on TV",
+                    hasDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel41));
+        }
+
+        if (MediaUtils.canDecodeVideo(MIMETYPE_VIDEO_HEVC, 3840, 2160, 30)) {
+            assertTrue(
+                    "H.265 must support Main10 Profile Main Tier Level 5 if UHD is supported",
+                    hasDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain10, HEVCMainTierLevel5));
+        }
+    }
+
     public void testAvcBaseline1() throws Exception {
-        if (!supports(AVC_MIME, CodecProfileLevel.AVCProfileBaseline)) {
-          return;
+        if (!checkDecoder(MIMETYPE_VIDEO_AVC, AVCProfileBaseline, AVCLevel1)) {
+            return; // skip
         }
-        if (!supports(AVC_MIME, CodecProfileLevel.AVCProfileBaseline,
-                CodecProfileLevel.AVCLevel1)) {
-            throw new RuntimeException("AVCLevel1 support is required by CDD");
-        }
-        // We don't have a test stream, but at least we're testing
-        // that supports() returns true for something.
+
+        // TODO: add a test stream
+        MediaUtils.skipTest(TAG, "no test stream");
     }
 
     public void testAvcBaseline12() throws Exception {
-        if (!supports(AVC_MIME, CodecProfileLevel.AVCProfileBaseline)) {
-            return;
-        }
-        if (!supports(AVC_MIME, CodecProfileLevel.AVCProfileBaseline,
-                CodecProfileLevel.AVCLevel12)) {
-            Log.i(TAG, "AvcBaseline12 not supported");
-            return;  // TODO: Can we make this mandatory?
+        if (!checkDecoder(MIMETYPE_VIDEO_AVC, AVCProfileBaseline, AVCLevel12)) {
+            return; // skip
         }
 
         playVideoWithRetries("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
@@ -67,13 +162,8 @@
     }
 
     public void testAvcBaseline30() throws Exception {
-        if (!supports(AVC_MIME, CodecProfileLevel.AVCProfileBaseline)) {
-            return;
-        }
-        if (!supports(AVC_MIME, CodecProfileLevel.AVCProfileBaseline,
-                CodecProfileLevel.AVCLevel3)) {
-            Log.i(TAG, "AvcBaseline30 not supported");
-            return;
+        if (!checkDecoder(MIMETYPE_VIDEO_AVC, AVCProfileBaseline, AVCLevel3)) {
+            return; // skip
         }
         playVideoWithRetries("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
                 + "&itag=18&source=youtube&user=android-device-test"
@@ -85,13 +175,8 @@
     }
 
     public void testAvcHigh31() throws Exception {
-        if (!supports(AVC_MIME, CodecProfileLevel.AVCProfileHigh)) {
-            return;
-        }
-        if (!supports(AVC_MIME, CodecProfileLevel.AVCProfileHigh,
-                CodecProfileLevel.AVCLevel31)) {
-            Log.i(TAG, "AvcHigh31 not supported");
-            return;
+        if (!checkDecoder(MIMETYPE_VIDEO_AVC, AVCProfileHigh, AVCLevel31)) {
+            return; // skip
         }
         playVideoWithRetries("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
                 + "&itag=22&source=youtube&user=android-device-test"
@@ -103,16 +188,11 @@
     }
 
     public void testAvcHigh40() throws Exception {
-        if (!supports(AVC_MIME, CodecProfileLevel.AVCProfileHigh)) {
-            return;
-        }
-        if (!supports(AVC_MIME, CodecProfileLevel.AVCProfileHigh,
-                CodecProfileLevel.AVCLevel4)) {
-            Log.i(TAG, "AvcHigh40 not supported");
-            return;
+        if (!checkDecoder(MIMETYPE_VIDEO_AVC, AVCProfileHigh, AVCLevel4)) {
+            return; // skip
         }
         if (Build.VERSION.SDK_INT < 18) {
-            Log.i(TAG, "fragmented mp4 not supported");
+            MediaUtils.skipTest(TAG, "fragmented mp4 not supported");
             return;
         }
         playVideoWithRetries("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
@@ -125,117 +205,131 @@
     }
 
     public void testHevcMain1() throws Exception {
-        if (!supports(HEVC_MIME, CodecProfileLevel.HEVCProfileMain,
-                CodecProfileLevel.HEVCMainTierLevel1)) {
-            throw new RuntimeException("HECLevel1 support is required by CDD");
+        if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel1)) {
+            return; // skip
         }
-        // We don't have a test stream, but at least we're testing
-        // that supports() returns true for something.
+
+        // TODO: add a test stream
+        MediaUtils.skipTest(TAG, "no test stream");
     }
+
     public void testHevcMain2() throws Exception {
-        if (!supports(HEVC_MIME, CodecProfileLevel.HEVCProfileMain,
-                CodecProfileLevel.HEVCMainTierLevel2)) {
-            Log.i(TAG, "HevcMain2 not supported");
-            return;
+        if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel2)) {
+            return; // skip
         }
+
+        // TODO: add a test stream
+        MediaUtils.skipTest(TAG, "no test stream");
     }
 
     public void testHevcMain21() throws Exception {
-        if (!supports(HEVC_MIME, CodecProfileLevel.HEVCProfileMain,
-                CodecProfileLevel.HEVCMainTierLevel21)) {
-            Log.i(TAG, "HevcMain21 not supported");
-            return;
+        if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel21)) {
+            return; // skip
         }
+
+        // TODO: add a test stream
+        MediaUtils.skipTest(TAG, "no test stream");
     }
 
     public void testHevcMain3() throws Exception {
-        if (!supports(HEVC_MIME, CodecProfileLevel.HEVCProfileMain,
-                CodecProfileLevel.HEVCMainTierLevel3)) {
-            Log.i(TAG, "HevcMain3 not supported");
-            return;
+        if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel3)) {
+            return; // skip
         }
+
+        // TODO: add a test stream
+        MediaUtils.skipTest(TAG, "no test stream");
     }
 
     public void testHevcMain31() throws Exception {
-        if (!supports(HEVC_MIME, CodecProfileLevel.HEVCProfileMain,
-                CodecProfileLevel.HEVCMainTierLevel31)) {
-            Log.i(TAG, "HevcMain31 not supported");
-            return;
+        if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel31)) {
+            return; // skip
         }
+
+        // TODO: add a test stream
+        MediaUtils.skipTest(TAG, "no test stream");
     }
 
     public void testHevcMain4() throws Exception {
-        if (!supports(HEVC_MIME, CodecProfileLevel.HEVCProfileMain,
-                CodecProfileLevel.HEVCMainTierLevel4)) {
-            Log.i(TAG, "HevcMain4 not supported");
-            return;
+        if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel4)) {
+            return; // skip
         }
+
+        // TODO: add a test stream
+        MediaUtils.skipTest(TAG, "no test stream");
     }
 
     public void testHevcMain41() throws Exception {
-        if (!supports(HEVC_MIME, CodecProfileLevel.HEVCProfileMain,
-                CodecProfileLevel.HEVCMainTierLevel41)) {
-            Log.i(TAG, "HevcMain41 not supported");
-            return;
+        if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel41)) {
+            return; // skip
         }
+
+        // TODO: add a test stream
+        MediaUtils.skipTest(TAG, "no test stream");
     }
 
     public void testHevcMain5() throws Exception {
-        if (!supports(HEVC_MIME, CodecProfileLevel.HEVCProfileMain,
-                CodecProfileLevel.HEVCMainTierLevel5)) {
-            Log.i(TAG, "HevcMain5 not supported");
-            return;
+        if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel5)) {
+            return; // skip
         }
+
+        // TODO: add a test stream
+        MediaUtils.skipTest(TAG, "no test stream");
     }
 
     public void testHevcMain51() throws Exception {
-        if (!supports(HEVC_MIME, CodecProfileLevel.HEVCProfileMain,
-                CodecProfileLevel.HEVCMainTierLevel51)) {
-            Log.i(TAG, "HevcMain51 not supported");
-            return;
+        if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel51)) {
+            return; // skip
         }
+
+        // TODO: add a test stream
+        MediaUtils.skipTest(TAG, "no test stream");
     }
 
-    private boolean supports(String mimeType, int profile) {
-        return supports(mimeType, profile, 0, false);
+    private boolean checkDecoder(String mime, int profile, int level) {
+        if (!hasDecoder(mime, profile, level)) {
+            MediaUtils.skipTest(TAG, "no " + mime + " decoder for profile "
+                    + profile + " and level " + level);
+            return false;
+        }
+        return true;
     }
 
-    private boolean supports(String mimeType, int profile, int level) {
-        return supports(mimeType, profile, level, true);
+    private boolean hasDecoder(String mime, int profile, int level) {
+        return supports(mime, false /* isEncoder */, profile, level);
     }
 
-    private boolean supports(String mimeType, int profile, int level, boolean testLevel) {
-        int numCodecs = MediaCodecList.getCodecCount();
-        for (int i = 0; i < numCodecs; i++) {
-            MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
-            if (codecInfo.isEncoder()) {
+    private boolean hasEncoder(String mime, int profile, int level) {
+        return supports(mime, true /* isEncoder */, profile, level);
+    }
+
+    private boolean supports(
+            String mime, boolean isEncoder, int profile, int level) {
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        for (MediaCodecInfo info : mcl.getCodecInfos()) {
+            if (isEncoder != info.isEncoder()) {
                 continue;
             }
+            try {
+                CodecCapabilities caps = info.getCapabilitiesForType(mime);
+                for (CodecProfileLevel pl : caps.profileLevels) {
+                    if (pl.profile != profile) {
+                        continue;
+                    }
 
-            if (!supportsMimeType(codecInfo, mimeType)) {
-                continue;
-            }
-
-            CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(mimeType);
-            for (CodecProfileLevel profileLevel : capabilities.profileLevels) {
-                if (profileLevel.profile == profile
-                        && (!testLevel || profileLevel.level >= level)) {
-                    return true;
+                    // H.263 levels are not completely ordered:
+                    // Level45 support only implies Level10 support
+                    if (mime.equalsIgnoreCase(MIMETYPE_VIDEO_H263)) {
+                        if (pl.level != level && pl.level == H263Level45 && level > H263Level10) {
+                            continue;
+                        }
+                    }
+                    if (pl.level >= level) {
+                        return true;
+                    }
                 }
-            }
-        }
-
-        return false;
-    }
-
-    private static boolean supportsMimeType(MediaCodecInfo codecInfo, String mimeType) {
-        String[] supportedMimeTypes = codecInfo.getSupportedTypes();
-        for (String supportedMimeType : supportedMimeTypes) {
-            if (mimeType.equalsIgnoreCase(supportedMimeType)) {
-                return true;
+            } catch (IllegalArgumentException e) {
             }
         }
         return false;
     }
-
 }
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecListTest.java b/tests/tests/media/src/android/media/cts/MediaCodecListTest.java
index 865780e..e04517d 100644
--- a/tests/tests/media/src/android/media/cts/MediaCodecListTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaCodecListTest.java
@@ -50,9 +50,10 @@
             mAllCodecs.getCodecInfos();
 
     class CodecType {
-        CodecType(String type, boolean isEncoder) {
+        CodecType(String type, boolean isEncoder, MediaFormat sampleFormat) {
             mMimeTypeName = type;
             mIsEncoder = isEncoder;
+            mSampleFormat = sampleFormat;
         }
 
         boolean equals(CodecType codecType) {
@@ -60,10 +61,35 @@
                     mIsEncoder == codecType.mIsEncoder;
         }
 
-        String mMimeTypeName;
-        boolean mIsEncoder;
+        boolean canBeFound() {
+            return codecCanBeFound(mIsEncoder, mSampleFormat);
+        }
+
+        @Override
+        public String toString() {
+            return mMimeTypeName + (mIsEncoder ? " encoder" : " decoder") + " for " + mSampleFormat;
+        }
+
+        private String mMimeTypeName;
+        private boolean mIsEncoder;
+        private MediaFormat mSampleFormat;
     };
 
+    class AudioCodec extends CodecType {
+        AudioCodec(String mime, boolean isEncoder, int sampleRate) {
+            super(mime, isEncoder, MediaFormat.createAudioFormat(
+                    mime, sampleRate, 1 /* channelCount */));
+        }
+    }
+
+    class VideoCodec extends CodecType {
+        VideoCodec(String mime, boolean isEncoder) {
+            // implicit assumption that QVGA video is always valid
+            super(mime, isEncoder, MediaFormat.createVideoFormat(
+                    mime, 176 /* width */, 144 /* height */));
+        }
+    }
+
     public static void testMediaCodecXmlFileExist() {
         File file = new File(MEDIA_CODEC_XML_FILE);
         assertTrue("/etc/media_codecs.xml does not exist", file.exists());
@@ -241,6 +267,9 @@
         List<CodecType> requiredList = getRequiredCodecTypes();
         List<CodecType> supportedList = getSupportedCodecTypes();
         assertTrue(areRequiredCodecTypesSupported(requiredList, supportedList));
+        for (CodecType type : requiredList) {
+            assertTrue("cannot find " + type, type.canBeFound());
+        }
     }
 
     private boolean hasCamera() {
@@ -249,80 +278,27 @@
                 pm.hasSystemFeature(pm.FEATURE_CAMERA);
     }
 
-    // H263 baseline profile must be supported
-    public void testIsH263BaselineProfileSupported() {
-        if (!hasCamera()) {
-            Log.d(TAG, "not required without camera");
-            return;
-        }
-
-        int profile = CodecProfileLevel.H263ProfileBaseline;
-        assertTrue(checkProfileSupported("video/3gpp", false, profile));
-        assertTrue(checkProfileSupported("video/3gpp", true, profile));
+    private boolean hasMicrophone() {
+        PackageManager pm = getContext().getPackageManager();
+        return pm.hasSystemFeature(pm.FEATURE_MICROPHONE);
     }
 
-    // AVC baseline profile must be supported
-    public void testIsAVCBaselineProfileSupported() {
-        int profile = CodecProfileLevel.AVCProfileBaseline;
-        assertTrue(checkProfileSupported("video/avc", false, profile));
-        assertTrue(checkProfileSupported("video/avc", true, profile));
+    private boolean isWatch() {
+        PackageManager pm = getContext().getPackageManager();
+        return pm.hasSystemFeature(pm.FEATURE_WATCH);
     }
 
-    // HEVC main profile must be supported
-    public void testIsHEVCMainProfileSupported() {
-        int profile = CodecProfileLevel.HEVCProfileMain;
-        assertTrue(checkProfileSupported("video/hevc", false, profile));
-    }
-
-    // MPEG4 simple profile must be supported
-    public void testIsM4VSimpleProfileSupported() {
-        if (!hasCamera()) {
-            Log.d(TAG, "not required without camera");
-            return;
-        }
-
-        int profile = CodecProfileLevel.MPEG4ProfileSimple;
-        assertTrue(checkProfileSupported("video/mp4v-es", false, profile));
-
-        // FIXME: no support for M4v simple profile video encoder
-        // assertTrue(checkProfileSupported("video/mp4v-es", true, profile));
-    }
-
-    /*
-     * Find whether the given codec is supported
-     */
-    private boolean checkProfileSupported(
-            String mime, boolean isEncoder, int profile) {
-        return profileIsListed(mime, isEncoder, profile) &&
-                codecCanBeFound(mime, isEncoder);
-    }
-
-    private boolean profileIsListed(
-        String mime, boolean isEncoder, int profile) {
-
-        for (MediaCodecInfo info : mRegularInfos) {
-            if (isEncoder != info.isEncoder()) {
-                continue;
-            }
-
-            for (String type : info.getSupportedTypes()) {
-                if (type.equalsIgnoreCase(mime)) {
-                    CodecCapabilities cap = info.getCapabilitiesForType(type);
-                    for (CodecProfileLevel pl : cap.profileLevels) {
-                        if (pl.profile == profile) {
-                            return true;
-                        }
-                    }
-                }
-            }
-        }
-        return false;
+    private boolean isHandheld() {
+        // handheld nature is not exposed to package manager, for now
+        // we check for touchscreen and NOT watch and NOT tv
+        PackageManager pm = getContext().getPackageManager();
+        return pm.hasSystemFeature(pm.FEATURE_TOUCHSCREEN)
+                && !pm.hasSystemFeature(pm.FEATURE_WATCH)
+                && !pm.hasSystemFeature(pm.FEATURE_TELEVISION);
     }
 
     // Find whether the given codec can be found using MediaCodecList.find methods.
-    private boolean codecCanBeFound(String mime, boolean isEncoder) {
-        // implicit assumption that QVGA video is always valid.
-        MediaFormat format = MediaFormat.createVideoFormat(mime, 176, 144);
+    private boolean codecCanBeFound(boolean isEncoder, MediaFormat format) {
         String codecName = isEncoder
                 ? mRegularCodecs.findEncoderForFormat(format)
                 : mRegularCodecs.findDecoderForFormat(format);
@@ -361,7 +337,7 @@
             assertTrue("Unexpected number of supported types", types.length > 0);
             boolean isEncoder = info.isEncoder();
             for (int j = 0; j < types.length; ++j) {
-                supportedList.add(new CodecType(types[j], isEncoder));
+                supportedList.add(new CodecType(types[j], isEncoder, null /* sampleFormat */));
             }
         }
         return supportedList;
@@ -374,30 +350,53 @@
     private List<CodecType> getRequiredCodecTypes() {
         List<CodecType> list = new ArrayList<CodecType>(16);
 
-        // Mandatory audio codecs
-        list.add(new CodecType("audio/amr-wb", false));         // amrwb decoder
-        list.add(new CodecType("audio/amr-wb", true));          // amrwb encoder
+        // Mandatory audio decoders
 
         // flac decoder is not omx-based yet
-        // list.add(new CodecType("audio/flac", false));        // flac decoder
-        list.add(new CodecType("audio/flac", true));            // flac encoder
-        list.add(new CodecType("audio/mpeg", false));           // mp3 decoder
-        list.add(new CodecType("audio/mp4a-latm", false));      // aac decoder
-        list.add(new CodecType("audio/mp4a-latm", true));       // aac encoder
-        list.add(new CodecType("audio/vorbis", false));         // vorbis decoder
-        list.add(new CodecType("audio/3gpp", false));           // amrnb decoder
-        list.add(new CodecType("audio/3gpp", true));            // amrnb encoder
+        // list.add(new CodecType(MediaFormat.MIMETYPE_AUDIO_FLAC, false, 8000));
+        // list.add(new CodecType(MediaFormat.MIMETYPE_AUDIO_FLAC, false, 48000));
+        list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_MPEG, false, 8000));  // mp3
+        list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_MPEG, false, 48000)); // mp3
+        list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_VORBIS, false, 8000));
+        list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_VORBIS, false, 48000));
+        list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_AAC, false, 8000));
+        list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_AAC, false, 48000));
+        list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_RAW, false, 8000));
+        list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_RAW, false, 44100));
+        list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_OPUS, false, 48000));
 
-        // Mandatory video codecs
-        list.add(new CodecType("video/avc", false));            // avc decoder
-        list.add(new CodecType("video/avc", true));             // avc encoder
-        list.add(new CodecType("video/hevc", false));           // hevc decoder
-        list.add(new CodecType("video/3gpp", false));           // h263 decoder
-        list.add(new CodecType("video/3gpp", true));            // h263 encoder
-        list.add(new CodecType("video/mp4v-es", false));        // m4v decoder
-        list.add(new CodecType("video/x-vnd.on2.vp8", false));  // vp8 decoder
-        list.add(new CodecType("video/x-vnd.on2.vp8", true));   // vp8 encoder
-        list.add(new CodecType("video/x-vnd.on2.vp9", false));  // vp9 decoder
+        // Mandatory audio encoders (for non-watch devices with camera)
+
+        if (hasMicrophone() && !isWatch()) {
+            list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_AAC, true, 16000));
+            list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_AAC, true, 48000));
+            // flac encoder is not required
+            // list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_FLAC, true));  // encoder
+        }
+
+        // Mandatory audio encoders for handheld devices
+        if (isHandheld()) {
+            list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_AMR_NB, false, 8000));  // decoder
+            list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_AMR_NB, true,  8000));  // encoder
+            list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_AMR_WB, false, 16000)); // decoder
+            list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_AMR_WB, true,  16000)); // encoder
+        }
+
+        // Mandatory video codecs (for non-watch devices)
+
+        if (!isWatch()) {
+            list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_AVC, false));   // avc decoder
+            list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_AVC, true));    // avc encoder
+            list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_VP8, false));   // vp8 decoder
+            list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_VP8, true));    // vp8 encoder
+            list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_VP9, false));   // vp9 decoder
+            list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_HEVC, false));  // hevc decoder
+            list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_MPEG4, false)); // m4v decoder
+            list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_H263, false));  // h263 decoder
+            if (hasCamera()) {
+                list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_H263, true)); // h263 encoder
+            }
+        }
 
         return list;
     }
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecTest.java b/tests/tests/media/src/android/media/cts/MediaCodecTest.java
index f72e3a0..937eee6 100644
--- a/tests/tests/media/src/android/media/cts/MediaCodecTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaCodecTest.java
@@ -19,6 +19,7 @@
 import com.android.cts.media.R;
 
 import android.content.res.AssetFileDescriptor;
+import android.cts.util.MediaUtils;
 import android.media.MediaCodec;
 import android.media.MediaCodecInfo;
 import android.media.MediaCodecList;
@@ -50,14 +51,15 @@
     private static final boolean VERBOSE = false;           // lots of logging
 
     // parameters for the video encoder
-    private static final String MIME_TYPE = "video/avc";    // H.264 Advanced Video Coding
+                                                            // H.264 Advanced Video Coding
+    private static final String MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_AVC;
     private static final int BIT_RATE = 2000000;            // 2Mbps
     private static final int FRAME_RATE = 15;               // 15fps
     private static final int IFRAME_INTERVAL = 10;          // 10 seconds between I-frames
     private static final int WIDTH = 1280;
     private static final int HEIGHT = 720;
     // parameters for the audio encoder
-    private static final String MIME_TYPE_AUDIO = "audio/mp4a-latm";
+    private static final String MIME_TYPE_AUDIO = MediaFormat.MIMETYPE_AUDIO_AAC;
     private static final int AUDIO_SAMPLE_RATE = 44100;
     private static final int AUDIO_AAC_PROFILE = 2; /* OMX_AUDIO_AACObjectLC */
     private static final int AUDIO_CHANNEL_COUNT = 2; // mono
@@ -79,21 +81,28 @@
      * methods when called in incorrect operational states.
      */
     public void testException() throws Exception {
-        MediaFormat[] formatList = new MediaFormat[2];
+        boolean tested = false;
+        // audio decoder (MP3 should be present on all Android devices)
+        MediaFormat format = MediaFormat.createAudioFormat(
+                MediaFormat.MIMETYPE_AUDIO_MPEG, 44100 /* sampleRate */, 2 /* channelCount */);
+        tested = verifyException(format, false /* isEncoder */) || tested;
 
-        // use audio format
-        formatList[0] = new MediaFormat();
-        formatList[0].setString(MediaFormat.KEY_MIME, "audio/amr-wb");
-        formatList[0].setInteger(MediaFormat.KEY_SAMPLE_RATE, 16000);
-        formatList[0].setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
-        formatList[0].setInteger(MediaFormat.KEY_BIT_RATE, 19850);
+        // audio encoder (AMR-WB may not be present on some Android devices)
+        format = MediaFormat.createAudioFormat(
+                MediaFormat.MIMETYPE_AUDIO_AMR_WB, 16000 /* sampleRate */, 1 /* channelCount */);
+        format.setInteger(MediaFormat.KEY_BIT_RATE, 19850);
+        tested = verifyException(format, true /* isEncoder */) || tested;
 
-        // use video format
-        formatList[1] = createMediaFormat();
+        // video decoder (H.264/AVC may not be present on some Android devices)
+        format = createMediaFormat();
+        tested = verifyException(format, false /* isEncoder */) || tested;
 
-        for (MediaFormat format : formatList) {
-            verifyIllegalStateException(format, false);
-            verifyIllegalStateException(format, true);
+        // video encoder (H.264/AVC may not be present on some Android devices)
+        tested = verifyException(format, true /* isEncoder */) || tested;
+
+        // signal test is skipped due to no device media codecs.
+        if (!tested) {
+            MediaUtils.skipTest(TAG, "cannot find any compatible device codecs");
         }
     }
 
@@ -116,11 +125,17 @@
         }
     }
 
-    private static void verifyIllegalStateException(MediaFormat format, boolean isEncoder)
+    private static boolean verifyException(MediaFormat format, boolean isEncoder)
             throws IOException {
-        MediaCodec codec;
+        String mimeType = format.getString(MediaFormat.KEY_MIME);
+        if (!supportsCodec(mimeType, isEncoder)) {
+            Log.i(TAG, "No " + (isEncoder ? "encoder" : "decoder")
+                    + " found for mimeType= " + mimeType);
+            return false;
+        }
 
         // create codec (enter Initialized State)
+        MediaCodec codec;
 
         // create improperly
         final String methodName = isEncoder ? "createEncoderByType" : "createDecoderByType";
@@ -263,6 +278,7 @@
             fail("stop should not return MediaCodec.CodecException on wrong state");
         } catch (IllegalStateException e) { // expected
         }
+        return true;
     }
 
     /**
@@ -272,6 +288,11 @@
      * <br> calling createInputSurface() with a non-Surface color format throws exception
      */
     public void testCreateInputSurfaceErrors() {
+        if (!supportsCodec(MIME_TYPE, true)) {
+            Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE);
+            return;
+        }
+
         MediaFormat format = createMediaFormat();
         MediaCodec encoder = null;
         Surface surface = null;
@@ -326,6 +347,11 @@
      * <br> submitting a frame after EOS throws exception [TODO]
      */
     public void testSignalSurfaceEOS() {
+        if (!supportsCodec(MIME_TYPE, true)) {
+            Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE);
+            return;
+        }
+
         MediaFormat format = createMediaFormat();
         MediaCodec encoder = null;
         InputSurface inputSurface = null;
@@ -378,6 +404,11 @@
      * <br> stopping with buffers in flight doesn't crash or hang
      */
     public void testAbruptStop() {
+        if (!supportsCodec(MIME_TYPE, true)) {
+            Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE);
+            return;
+        }
+
         // There appears to be a race, so run it several times with a short delay between runs
         // to allow any previous activity to shut down.
         for (int i = 0; i < 50; i++) {
@@ -432,6 +463,11 @@
      * <br> dequeueInputBuffer() fails when encoder configured with an input Surface
      */
     public void testDequeueSurface() {
+        if (!supportsCodec(MIME_TYPE, true)) {
+            Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE);
+            return;
+        }
+
         MediaFormat format = createMediaFormat();
         MediaCodec encoder = null;
         Surface surface = null;
@@ -470,6 +506,11 @@
      * <br> sending EOS with signalEndOfInputStream on non-Surface encoder fails
      */
     public void testReconfigureWithoutSurface() {
+        if (!supportsCodec(MIME_TYPE, true)) {
+            Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE);
+            return;
+        }
+
         MediaFormat format = createMediaFormat();
         MediaCodec encoder = null;
         Surface surface = null;
@@ -553,8 +594,13 @@
             mediaExtractor = getMediaExtractorForMimeType(inputResourceId, "video/");
             MediaFormat mediaFormat =
                     mediaExtractor.getTrackFormat(mediaExtractor.getSampleTrackIndex());
+            String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME);
+            if (!supportsCodec(mimeType, false)) {
+                Log.i(TAG, "No decoder found for mimeType= " + MIME_TYPE);
+                return true;
+            }
             mediaCodec =
-                    MediaCodec.createDecoderByType(mediaFormat.getString(MediaFormat.KEY_MIME));
+                    MediaCodec.createDecoderByType(mimeType);
             mediaCodec.configure(mediaFormat, outputSurface.getSurface(), null, 0);
             mediaCodec.start();
             boolean eos = false;
@@ -669,6 +715,16 @@
      * Tests creating an encoder and decoder for {@link #MIME_TYPE_AUDIO} at the same time.
      */
     public void testCreateAudioDecoderAndEncoder() {
+        if (!supportsCodec(MIME_TYPE_AUDIO, true)) {
+            Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE_AUDIO);
+            return;
+        }
+
+        if (!supportsCodec(MIME_TYPE_AUDIO, false)) {
+            Log.i(TAG, "No decoder found for mimeType= " + MIME_TYPE_AUDIO);
+            return;
+        }
+
         final MediaFormat encoderFormat = MediaFormat.createAudioFormat(
                 MIME_TYPE_AUDIO, AUDIO_SAMPLE_RATE, AUDIO_CHANNEL_COUNT);
         encoderFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, AUDIO_AAC_PROFILE);
@@ -716,6 +772,16 @@
     }
 
     public void testConcurrentAudioVideoEncodings() throws InterruptedException {
+        if (!supportsCodec(MIME_TYPE_AUDIO, true)) {
+            Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE_AUDIO);
+            return;
+        }
+
+        if (!supportsCodec(MIME_TYPE, true)) {
+            Log.i(TAG, "No decoder found for mimeType= " + MIME_TYPE);
+            return;
+        }
+
         final int VIDEO_NUM_SWAPS = 100;
         // audio only checks this and stop
         mVideoEncodingOngoing = true;
@@ -948,18 +1014,17 @@
      * match was found.
      */
     private static MediaCodecInfo selectCodec(String mimeType) {
-        int numCodecs = MediaCodecList.getCodecCount();
-        for (int i = 0; i < numCodecs; i++) {
-            MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
-
-            if (!codecInfo.isEncoder()) {
+        // FIXME: select codecs based on the complete use-case, not just the mime
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        for (MediaCodecInfo info : mcl.getCodecInfos()) {
+            if (!info.isEncoder()) {
                 continue;
             }
 
-            String[] types = codecInfo.getSupportedTypes();
+            String[] types = info.getSupportedTypes();
             for (int j = 0; j < types.length; j++) {
                 if (types[j].equalsIgnoreCase(mimeType)) {
-                    return codecInfo;
+                    return info;
                 }
             }
         }
@@ -1006,4 +1071,23 @@
 
         return mediaExtractor;
     }
-}
+
+    private static boolean supportsCodec(String mimeType, boolean encoder) {
+        MediaCodecList list = new MediaCodecList(MediaCodecList.ALL_CODECS);
+        for (MediaCodecInfo info : list.getCodecInfos()) {
+            if (encoder && !info.isEncoder()) {
+                continue;
+            }
+            if (!encoder && info.isEncoder()) {
+                continue;
+            }
+            
+            for (String type : info.getSupportedTypes()) {
+                if (type.equalsIgnoreCase(mimeType)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/media/src/android/media/cts/MediaMuxerTest.java b/tests/tests/media/src/android/media/cts/MediaMuxerTest.java
index 6591555..67eeca0 100644
--- a/tests/tests/media/src/android/media/cts/MediaMuxerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaMuxerTest.java
@@ -97,7 +97,7 @@
 
         // Throws exception b/c start() is not called.
         muxer = new MediaMuxer(outputFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
-        muxer.addTrack(MediaFormat.createVideoFormat("video/avc", 480, 320));
+        muxer.addTrack(MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 480, 320));
 
         try {
             muxer.stop();
@@ -108,10 +108,10 @@
 
         // Throws exception b/c 2 video tracks were added.
         muxer = new MediaMuxer(outputFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
-        muxer.addTrack(MediaFormat.createVideoFormat("video/avc", 480, 320));
+        muxer.addTrack(MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 480, 320));
 
         try {
-            muxer.addTrack(MediaFormat.createVideoFormat("video/avc", 480, 320));
+            muxer.addTrack(MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 480, 320));
             fail("should throw IllegalStateException.");
         } catch (IllegalStateException e) {
             // expected
@@ -119,9 +119,9 @@
 
         // Throws exception b/c 2 audio tracks were added.
         muxer = new MediaMuxer(outputFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
-        muxer.addTrack(MediaFormat.createAudioFormat("audio/mp4a-latm", 48000, 1));
+        muxer.addTrack(MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, 48000, 1));
         try {
-            muxer.addTrack(MediaFormat.createAudioFormat("audio/mp4a-latm", 48000, 1));
+            muxer.addTrack(MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, 48000, 1));
             fail("should throw IllegalStateException.");
         } catch (IllegalStateException e) {
             // expected
@@ -129,11 +129,11 @@
 
         // Throws exception b/c 3 tracks were added.
         muxer = new MediaMuxer(outputFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
-        muxer.addTrack(MediaFormat.createVideoFormat("video/avc", 480, 320));
-        muxer.addTrack(MediaFormat.createAudioFormat("audio/mp4a-latm", 48000, 1));
+        muxer.addTrack(MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 480, 320));
+        muxer.addTrack(MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, 48000, 1));
         try {
 
-            muxer.addTrack(MediaFormat.createVideoFormat("video/avc", 480, 320));
+            muxer.addTrack(MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 480, 320));
             fail("should throw IllegalStateException.");
         } catch (IllegalStateException e) {
             // expected
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayerFlakyNetworkTest.java b/tests/tests/media/src/android/media/cts/MediaPlayerFlakyNetworkTest.java
index 8063cbb..32fbfb5 100644
--- a/tests/tests/media/src/android/media/cts/MediaPlayerFlakyNetworkTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaPlayerFlakyNetworkTest.java
@@ -15,6 +15,7 @@
  */
 package android.media.cts;
 
+import android.cts.util.MediaUtils;
 import android.media.MediaPlayer;
 import android.os.Handler;
 import android.os.Looper;
@@ -34,6 +35,7 @@
 
 import java.net.Socket;
 import java.util.Random;
+import java.util.Vector;
 import java.util.concurrent.Callable;
 import java.util.concurrent.FutureTask;
 
@@ -42,6 +44,7 @@
  * from an HTTP server over a simulated "flaky" network.
  */
 public class MediaPlayerFlakyNetworkTest extends MediaPlayerTestBase {
+    private static final String PKG = "com.android.cts.media";
 
     private static final String[] TEST_VIDEOS = {
         "raw/video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz",
@@ -92,21 +95,37 @@
         doPlayStreams(6, 0.00002f);
     }
 
-   private void doPlayStreams(int seed, float probability) throws Throwable {
+    private String[] getSupportedVideos() {
+        Vector<String> supported = new Vector<String>();
+        for (String video : TEST_VIDEOS) {
+            if (MediaUtils.hasCodecsForPath(mContext, "android.resource://" + PKG + "/" + video)) {
+                supported.add(video);
+            }
+        }
+        return supported.toArray(new String[supported.size()]);
+    }
+
+    private void doPlayStreams(int seed, float probability) throws Throwable {
+        String[] supported = getSupportedVideos();
+        if (supported.length == 0) {
+            MediaUtils.skipTest("no codec found");
+            return;
+        }
+
         Random random = new Random(seed);
         createHttpServer(seed, probability);
         for (int i = 0; i < 10; i++) {
-            String video = getRandomTestVideo(random);
+            String video = getRandomTestVideo(random, supported);
             doPlayMp4Stream(video, 20000, 5000);
             doAsyncPrepareAndRelease(video);
             doRandomOperations(video);
         }
-        doPlayMp4Stream(getRandomTestVideo(random), 30000, 20000);
+        doPlayMp4Stream(getRandomTestVideo(random, supported), 30000, 20000);
         releaseHttpServer();
     }
 
-    private String getRandomTestVideo(Random random) {
-        return TEST_VIDEOS[random.nextInt(TEST_VIDEOS.length)];
+    private String getRandomTestVideo(Random random, String[] videos) {
+        return videos[random.nextInt(videos.length)];
     }
 
     private void doPlayMp4Stream(String video, int millisToPrepare, int millisToPlay)
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayerTest.java b/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
index 799fd59..7cdc483 100644
--- a/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
@@ -20,7 +20,11 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.res.AssetFileDescriptor;
+import android.cts.util.MediaUtils;
 import android.media.AudioManager;
+import android.media.MediaCodec;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
 import android.media.MediaPlayer;
 import android.media.MediaPlayer.OnErrorListener;
 import android.media.MediaRecorder;
@@ -375,6 +379,11 @@
     }
 
     public void testPlayAudioTwice() throws Exception {
+        if (!hasAudioOutput()) {
+            Log.i(LOG_TAG, "SKIPPING testPlayAudioTwice(). No audio output.");
+            return;
+        }
+
         final int resid = R.raw.camera_click;
 
         MediaPlayer mp = MediaPlayer.create(mContext, resid);
@@ -621,6 +630,10 @@
     }
 
     private void testGapless(int resid1, int resid2) throws Exception {
+        if (!hasAudioOutput()) {
+            Log.i(LOG_TAG, "SKIPPING testPlayAudioTwice(). No audio output.");
+            return;
+        }
 
         MediaPlayer mp1 = new MediaPlayer();
         mp1.setAudioStreamType(AudioManager.STREAM_MUSIC);
@@ -731,7 +744,9 @@
             }
         });
 
-        loadResource(R.raw.testvideo);
+        if (!checkLoadResource(R.raw.testvideo)) {
+            return; // skip;
+        }
         playLoadedVideo(352, 288, -1);
 
         Thread.sleep(SLEEP_TIME);
@@ -1082,7 +1097,9 @@
     }
 
     public void testDeselectTrack() throws Throwable {
-        loadResource(R.raw.testvideo_with_2_subtitles);
+        if (!checkLoadResource(R.raw.testvideo_with_2_subtitles)) {
+            return; // skip;
+        }
         runTestOnUiThread(new Runnable() {
             public void run() {
                 try {
@@ -1153,7 +1170,9 @@
     }
 
     public void testChangeSubtitleTrack() throws Throwable {
-        loadResource(R.raw.testvideo_with_2_subtitles);
+        if (!checkLoadResource(R.raw.testvideo_with_2_subtitles)) {
+            return; // skip;
+        }
 
         mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
         mMediaPlayer.setScreenOnWhilePlaying(true);
@@ -1241,7 +1260,9 @@
     }
 
     public void testGetTrackInfo() throws Throwable {
-        loadResource(R.raw.testvideo_with_2_subtitles);
+        if (!checkLoadResource(R.raw.testvideo_with_2_subtitles)) {
+            return; // skip;
+        }
         runTestOnUiThread(new Runnable() {
             public void run() {
                 try {
@@ -1283,17 +1304,23 @@
      *  The ones being used here are 10 seconds long.
      */
     public void testResumeAtEnd() throws Throwable {
-        testResumeAtEnd(R.raw.loudsoftmp3);
-        testResumeAtEnd(R.raw.loudsoftwav);
-        testResumeAtEnd(R.raw.loudsoftogg);
-        testResumeAtEnd(R.raw.loudsoftitunes);
-        testResumeAtEnd(R.raw.loudsoftfaac);
-        testResumeAtEnd(R.raw.loudsoftaac);
+        int testsRun =
+            testResumeAtEnd(R.raw.loudsoftmp3) +
+            testResumeAtEnd(R.raw.loudsoftwav) +
+            testResumeAtEnd(R.raw.loudsoftogg) +
+            testResumeAtEnd(R.raw.loudsoftitunes) +
+            testResumeAtEnd(R.raw.loudsoftfaac) +
+            testResumeAtEnd(R.raw.loudsoftaac);
+        if (testsRun == 0) {
+            MediaUtils.skipTest("no decoder found");
+        }
     }
 
-    private void testResumeAtEnd(int res) throws Throwable {
-
-        loadResource(res);
+    // returns 1 if test was run, 0 otherwise
+    private int testResumeAtEnd(int res) throws Throwable {
+        if (!loadResource(res)) {
+            return 0; // skip
+        }
         mMediaPlayer.prepare();
         mOnCompletionCalled.reset();
         mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@@ -1311,12 +1338,16 @@
         assertTrue("MediaPlayer should still be playing", mMediaPlayer.isPlaying());
         mMediaPlayer.reset();
         assertEquals("wrong number of repetitions", 1, mOnCompletionCalled.getNumSignal());
+        return 1;
     }
 
     public void testCallback() throws Throwable {
         final int mp4Duration = 8484;
 
-        loadResource(R.raw.testvideo);
+        if (!checkLoadResource(R.raw.testvideo)) {
+            return; // skip;
+        }
+
         mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
         mMediaPlayer.setScreenOnWhilePlaying(true);
 
@@ -1388,8 +1419,13 @@
 
     public void testRecordAndPlay() throws Exception {
         if (!hasMicrophone()) {
+            MediaUtils.skipTest(LOG_TAG, "no microphone");
             return;
         }
+        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_AUDIO_AMR_NB)
+                || !MediaUtils.checkEncoder(MediaFormat.MIMETYPE_AUDIO_AMR_NB)) {
+            return; // skip
+        }
         File outputFile = new File(Environment.getExternalStorageDirectory(),
                 "record_and_play.3gp");
         String outputFileLocation = outputFile.getAbsolutePath();
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayerTestBase.java b/tests/tests/media/src/android/media/cts/MediaPlayerTestBase.java
index 61d8792..d089658 100644
--- a/tests/tests/media/src/android/media/cts/MediaPlayerTestBase.java
+++ b/tests/tests/media/src/android/media/cts/MediaPlayerTestBase.java
@@ -16,8 +16,10 @@
 package android.media.cts;
 
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.content.res.AssetFileDescriptor;
 import android.content.res.Resources;
+import android.cts.util.MediaUtils;
 import android.media.MediaPlayer;
 import android.test.ActivityInstrumentationTestCase2;
 
@@ -142,7 +144,12 @@
         super.tearDown();
     }
 
-    protected void loadResource(int resid) throws Exception {
+    // returns true on success
+    protected boolean loadResource(int resid) throws Exception {
+        if (!MediaUtils.hasCodecsForResource(mContext, resid)) {
+            return false;
+        }
+
         AssetFileDescriptor afd = mResources.openRawResourceFd(resid);
         try {
             mMediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(),
@@ -159,6 +166,11 @@
             afd.close();
         }
         sUseScaleToFitMode = !sUseScaleToFitMode;  // Alternate the scaling mode
+        return true;
+    }
+
+    protected boolean checkLoadResource(int resid) throws Exception {
+        return MediaUtils.check(loadResource(resid), "no decoder found");
     }
 
     protected void loadSubtitleSource(int resid) throws Exception {
@@ -197,7 +209,10 @@
     }
 
     protected void playVideoTest(int resid, int width, int height) throws Exception {
-        loadResource(resid);
+        if (!checkLoadResource(resid)) {
+            return; // skip
+        }
+
         playLoadedVideo(width, height, 0);
     }
 
@@ -278,4 +293,19 @@
     }
 
     private static class PrepareFailedException extends Exception {}
+
+    public boolean hasAudioOutput() {
+        return getInstrumentation().getTargetContext().getPackageManager()
+            .hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT);
+    }
+
+    public boolean isTv() {
+        PackageManager pm = getInstrumentation().getTargetContext().getPackageManager();
+        return pm.hasSystemFeature(pm.FEATURE_TELEVISION)
+                && pm.hasSystemFeature(pm.FEATURE_LEANBACK);
+    }
+
+    public boolean checkTv() {
+        return MediaUtils.check(isTv(), "not a TV");
+    }
 }
diff --git a/tests/tests/media/src/android/media/cts/MediaRecorderTest.java b/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
index 3cc71fa..78b5cfd 100644
--- a/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
@@ -17,7 +17,9 @@
 
 
 import android.content.pm.PackageManager;
+import android.cts.util.MediaUtils;
 import android.hardware.Camera;
+import android.media.MediaFormat;
 import android.media.MediaMetadataRetriever;
 import android.media.MediaRecorder;
 import android.media.MediaRecorder.OnErrorListener;
@@ -306,30 +308,40 @@
     }
 
     public void testRecordingAudioInRawFormats() throws Exception {
-        testRecordAudioInRawFormat(
-                MediaRecorder.OutputFormat.AMR_NB,
-                MediaRecorder.AudioEncoder.AMR_NB);
+        int testsRun = 0;
+        if (hasAmrNb()) {
+            testsRun += testRecordAudioInRawFormat(
+                    MediaRecorder.OutputFormat.AMR_NB,
+                    MediaRecorder.AudioEncoder.AMR_NB);
+        }
 
-        testRecordAudioInRawFormat(
-                MediaRecorder.OutputFormat.AMR_WB,
-                MediaRecorder.AudioEncoder.AMR_WB);
+        if (hasAmrWb()) {
+            testsRun += testRecordAudioInRawFormat(
+                    MediaRecorder.OutputFormat.AMR_WB,
+                    MediaRecorder.AudioEncoder.AMR_WB);
+        }
 
-        testRecordAudioInRawFormat(
-                MediaRecorder.OutputFormat.AAC_ADTS,
-                MediaRecorder.AudioEncoder.AAC);
+        if (hasAac()) {
+            testsRun += testRecordAudioInRawFormat(
+                    MediaRecorder.OutputFormat.AAC_ADTS,
+                    MediaRecorder.AudioEncoder.AAC);
+        }
+        if (testsRun == 0) {
+            MediaUtils.skipTest("no audio codecs or microphone");
+        }
     }
 
-    private void testRecordAudioInRawFormat(
+    private int testRecordAudioInRawFormat(
             int fileFormat, int codec) throws Exception {
-
         if (!hasMicrophone()) {
-            return;
+            return 0; // skip
         }
         mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
         mMediaRecorder.setOutputFormat(fileFormat);
         mMediaRecorder.setOutputFile(OUTPUT_PATH);
         mMediaRecorder.setAudioEncoder(codec);
         recordMedia(MAX_FILE_SIZE, mOutFile);
+        return 1;
     }
 
     public void testGetAudioSourceMax() throws Exception {
@@ -345,7 +357,8 @@
     }
 
     public void testRecorderAudio() throws Exception {
-        if (!hasMicrophone()) {
+        if (!hasMicrophone() || !hasAmrNb()) {
+            MediaUtils.skipTest("no audio codecs or microphone");
             return;
         }
         mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
@@ -360,7 +373,8 @@
     }
 
     public void testOnInfoListener() throws Exception {
-        if (!hasMicrophone()) {
+        if (!hasMicrophone() || !hasAmrNb()) {
+            MediaUtils.skipTest("no audio codecs or microphone");
             return;
         }
         mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
@@ -374,7 +388,8 @@
     }
 
     public void testSetMaxDuration() throws Exception {
-        if (!hasMicrophone()) {
+        if (!hasMicrophone() || !hasAmrNb()) {
+            MediaUtils.skipTest("no audio codecs or microphone");
             return;
         }
         testSetMaxDuration(RECORD_TIME_LONG_MS, RECORDED_DUR_TOLERANCE_MS);
@@ -412,14 +427,15 @@
     }
 
     public void testSetMaxFileSize() throws Exception {
-        if (!hasMicrophone() || !hasCamera()) {
-            return;
-        }
         testSetMaxFileSize(512 * 1024, 50 * 1024);
     }
 
     private void testSetMaxFileSize(
             long fileSize, long tolerance) throws Exception {
+        if (!hasMicrophone() || !hasCamera() || !hasAmrNb() || !hasH264()) {
+            MediaUtils.skipTest("no microphone, camera, or codecs");
+            return;
+        }
         mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
         mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
         mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
@@ -448,7 +464,8 @@
     }
 
     public void testOnErrorListener() throws Exception {
-        if (!hasMicrophone()) {
+        if (!hasMicrophone() || !hasAmrNb()) {
+            MediaUtils.skipTest("no audio codecs or microphone");
             return;
         }
         mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT);
@@ -483,4 +500,20 @@
         return mActivity.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_MICROPHONE);
     }
+
+    private static boolean hasAmrNb() {
+        return MediaUtils.hasEncoder(MediaFormat.MIMETYPE_AUDIO_AMR_NB);
+    }
+
+    private static boolean hasAmrWb() {
+        return MediaUtils.hasEncoder(MediaFormat.MIMETYPE_AUDIO_AMR_WB);
+    }
+
+    private static boolean hasAac() {
+        return MediaUtils.hasEncoder(MediaFormat.MIMETYPE_AUDIO_AAC);
+    }
+
+    private static boolean hasH264() {
+        return MediaUtils.hasEncoder(MediaFormat.MIMETYPE_VIDEO_AVC);
+    }
 }
diff --git a/tests/tests/media/src/android/media/cts/NativeDecoderTest.java b/tests/tests/media/src/android/media/cts/NativeDecoderTest.java
index fc27dfa..fea78c9 100644
--- a/tests/tests/media/src/android/media/cts/NativeDecoderTest.java
+++ b/tests/tests/media/src/android/media/cts/NativeDecoderTest.java
@@ -20,6 +20,7 @@
 
 import android.content.res.AssetFileDescriptor;
 import android.content.res.Resources;
+import android.cts.util.MediaUtils;
 import android.media.MediaCodec;
 import android.media.MediaCodec.BufferInfo;
 import android.media.MediaCodecInfo;
@@ -90,8 +91,8 @@
         testExtractor(R.raw.sinesweepwav);
 
         testExtractor(R.raw.video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz);
-        testExtractor(R.raw.video_1280x720_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz);
-        testExtractor(R.raw.video_1280x720_webm_vp9_309kbps_25fps_vorbis_stereo_128kbps_44100hz);
+        testExtractor(R.raw.video_1280x720_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_48000hz);
+        testExtractor(R.raw.video_1280x720_webm_vp9_309kbps_25fps_vorbis_stereo_128kbps_48000hz);
         testExtractor(R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz);
         testExtractor(R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz);
 
@@ -183,22 +184,29 @@
 
 
     public void testDecoder() throws Exception {
-        testDecoder(R.raw.sinesweepogg);
-        testDecoder(R.raw.sinesweepmp3lame);
-        testDecoder(R.raw.sinesweepmp3smpb);
-        testDecoder(R.raw.sinesweepm4a);
-        testDecoder(R.raw.sinesweepflac);
-        testDecoder(R.raw.sinesweepwav);
+        int testsRun =
+            testDecoder(R.raw.sinesweepogg) +
+            testDecoder(R.raw.sinesweepmp3lame) +
+            testDecoder(R.raw.sinesweepmp3smpb) +
+            testDecoder(R.raw.sinesweepm4a) +
+            testDecoder(R.raw.sinesweepflac) +
+            testDecoder(R.raw.sinesweepwav) +
 
-        testDecoder(R.raw.video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz);
-        testDecoder(R.raw.video_1280x720_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz);
-        testDecoder(R.raw.video_1280x720_webm_vp9_309kbps_25fps_vorbis_stereo_128kbps_44100hz);
-        testDecoder(R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz);
-        testDecoder(R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz);
-
+            testDecoder(R.raw.video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz) +
+            testDecoder(R.raw.video_1280x720_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_48000hz) +
+            testDecoder(R.raw.video_1280x720_webm_vp9_309kbps_25fps_vorbis_stereo_128kbps_48000hz) +
+            testDecoder(R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz) +
+            testDecoder(R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz);
+        if (testsRun == 0) {
+            MediaUtils.skipTest("no decoders found");
+        }
     }
 
-    private void testDecoder(int res) throws Exception {
+    private int testDecoder(int res) throws Exception {
+        if (!MediaUtils.hasCodecsForResource(mContext, res)) {
+            return 0; // skip
+        }
+
         AssetFileDescriptor fd = mResources.openRawResourceFd(res);
 
         int[] jdata = getDecodedData(
@@ -211,6 +219,7 @@
         Log.i("@@@", Arrays.toString(ndata));
         assertEquals("number of samples differs", jdata.length, ndata.length);
         assertTrue("different decoded data", Arrays.equals(jdata, ndata));
+        return 1;
     }
 
     private static int[] getDecodedData(FileDescriptor fd, long offset, long size)
@@ -331,6 +340,10 @@
             }
         }
 
+        for (int i = 0; i < codec.length; i++) {
+            codec[i].release();
+        }
+
         return trackbytes;
     }
 
@@ -374,19 +387,33 @@
             throws IOException;
 
     public void testVideoPlayback() throws Exception {
-        testVideoPlayback(R.raw.video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz);
-        testVideoPlayback(R.raw.video_1280x720_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz);
-        testVideoPlayback(R.raw.video_1280x720_webm_vp9_309kbps_25fps_vorbis_stereo_128kbps_44100hz);
-        testVideoPlayback(R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz);
-        testVideoPlayback(R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz);
+        int testsRun =
+            testVideoPlayback(
+                    R.raw.video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz) +
+            testVideoPlayback(
+                    R.raw.video_1280x720_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_48000hz) +
+            testVideoPlayback(
+                    R.raw.video_1280x720_webm_vp9_309kbps_25fps_vorbis_stereo_128kbps_48000hz) +
+            testVideoPlayback(
+                    R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz) +
+            testVideoPlayback(
+                    R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz);
+        if (testsRun == 0) {
+            MediaUtils.skipTest("no decoders found");
+        }
     }
 
-    private void testVideoPlayback(int res) throws Exception {
+    private int testVideoPlayback(int res) throws Exception {
+        if (!MediaUtils.checkCodecsForResource(mContext, res)) {
+            return 0; // skip
+        }
+
         AssetFileDescriptor fd = mResources.openRawResourceFd(res);
 
         boolean ret = testPlaybackNative(mActivity.getSurfaceHolder().getSurface(),
                 fd.getParcelFileDescriptor().getFd(), fd.getStartOffset(), fd.getLength());
         assertTrue("native playback error", ret);
+        return 1;
     }
 
     private static native boolean testPlaybackNative(Surface surface,
@@ -397,6 +424,10 @@
     }
 
     private void testMuxer(int res, boolean webm) throws Exception {
+        if (!MediaUtils.checkCodecsForResource(mContext, res)) {
+            return; // skip
+        }
+
         AssetFileDescriptor infd = mResources.openRawResourceFd(res);
 
         File base = mContext.getExternalFilesDir(null);
diff --git a/tests/tests/media/src/android/media/cts/PresentationSyncTest.java b/tests/tests/media/src/android/media/cts/PresentationSyncTest.java
index ac86f1e..efa2e0a 100644
--- a/tests/tests/media/src/android/media/cts/PresentationSyncTest.java
+++ b/tests/tests/media/src/android/media/cts/PresentationSyncTest.java
@@ -109,7 +109,20 @@
         CpuWaster cpuWaster = new CpuWaster();
         try {
             cpuWaster.start();
-            runThroughputTest(output, refreshNsec, 0.75f);
+            // Tests with mult < 1.0f are flaky, for two reasons:
+            //
+            // (a) They assume that the GPU can render the test scene in less than mult*refreshNsec.
+            //     It's a simple scene, but CTS/CDD don't currently require being able to do more
+            //     than a full-screen clear in refreshNsec.
+            //
+            // (b) More importantly, it assumes that the only rate-limiting happening is
+            //     backpressure from the buffer queue. If the EGL implementation is doing its own
+            //     rate-limiting (to limit the amount of work queued to the GPU at any time), then
+            //     depending on how that's implemented the buffer queue may not have two frames
+            //     pending very often. So the consumer won't be able to drop many frames, and the
+            //     throughput won't be much better than with mult=1.0.
+            //
+            // runThroughputTest(output, refreshNsec, 0.75f);
             cpuWaster.stop();
             runThroughputTest(output, refreshNsec, 1.0f);
             runThroughputTest(output, refreshNsec, 2.0f);
diff --git a/tests/tests/media/src/android/media/cts/RingtoneManagerTest.java b/tests/tests/media/src/android/media/cts/RingtoneManagerTest.java
index dfaabb8..bf47a27 100644
--- a/tests/tests/media/src/android/media/cts/RingtoneManagerTest.java
+++ b/tests/tests/media/src/android/media/cts/RingtoneManagerTest.java
@@ -21,6 +21,7 @@
 import android.app.Activity;
 import android.app.Instrumentation;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.media.AudioManager;
 import android.media.Ringtone;
@@ -28,11 +29,13 @@
 import android.net.Uri;
 import android.provider.Settings;
 import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
 
 public class RingtoneManagerTest
         extends ActivityInstrumentationTestCase2<RingtonePickerActivity> {
 
     private static final String PKG = "com.android.cts.media";
+    private static final String TAG = "RingtoneManagerTest";
 
     private RingtonePickerActivity mActivity;
     private Instrumentation mInstrumentation;
@@ -74,12 +77,21 @@
         super.tearDown();
     }
 
+    private boolean hasAudioOutput() {
+        return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT);
+    }
+
     public void testConstructors() {
         new RingtoneManager(mActivity);
         new RingtoneManager(mContext);
     }
 
     public void testAccessMethods() {
+        if (!hasAudioOutput()) {
+            Log.i(TAG, "Skipping testAccessMethods(): device doesn't have audio output.");
+            return;
+        }
+
         Cursor c = mRingtoneManager.getCursor();
         assertTrue("Must have at least one ring tone available", c.getCount() > 0);
 
@@ -115,6 +127,11 @@
     }
 
     public void testStopPreviousRingtone() {
+        if (!hasAudioOutput()) {
+            Log.i(TAG, "Skipping testStopPreviousRingtone(): device doesn't have audio output.");
+            return;
+        }
+
         Cursor c = mRingtoneManager.getCursor();
         assertTrue("Must have at least one ring tone available", c.getCount() > 0);
 
diff --git a/tests/tests/media/src/android/media/cts/RingtoneTest.java b/tests/tests/media/src/android/media/cts/RingtoneTest.java
index 6e3a1e9..f5218e3 100644
--- a/tests/tests/media/src/android/media/cts/RingtoneTest.java
+++ b/tests/tests/media/src/android/media/cts/RingtoneTest.java
@@ -16,16 +16,18 @@
 
 package android.media.cts;
 
-
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.media.AudioManager;
 import android.media.Ringtone;
 import android.media.RingtoneManager;
 import android.net.Uri;
 import android.provider.Settings;
 import android.test.AndroidTestCase;
+import android.util.Log;
 
 public class RingtoneTest extends AndroidTestCase {
+    private static final String TAG = "RingtoneTest";
 
     private Context mContext;
     private Ringtone mRingtone;
@@ -73,7 +75,16 @@
         super.tearDown();
     }
 
+    private boolean hasAudioOutput() {
+        return getContext().getPackageManager()
+            .hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT);
+    }
+
     public void testRingtone() {
+        if (!hasAudioOutput()) {
+            Log.i(TAG, "Skipping testRingtone(): device doesn't have audio output.");
+            return;
+        }
 
         assertNotNull(mRingtone.getTitle(mContext));
         assertTrue(mOriginalStreamType >= 0);
diff --git a/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java b/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java
index 419f3e0..dd7c1f6 100644
--- a/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java
+++ b/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java
@@ -15,6 +15,8 @@
  */
 package android.media.cts;
 
+import android.cts.util.MediaUtils;
+import android.media.MediaFormat;
 import android.media.MediaPlayer;
 import android.os.Looper;
 import android.os.SystemClock;
@@ -23,11 +25,12 @@
 
 import java.io.IOException;
 
-
 /**
  * Tests of MediaPlayer streaming capabilities.
  */
 public class StreamingMediaPlayerTest extends MediaPlayerTestBase {
+    private static final String TAG = "StreamingMediaPlayerTest";
+
     private CtsTestServer mServer;
 
 /* RTSP tests are more flaky and vulnerable to network condition.
@@ -62,6 +65,10 @@
 */
     // Streaming HTTP video from YouTube
     public void testHTTP_H263_AMR_Video1() throws Exception {
+        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_H263, MediaFormat.MIMETYPE_AUDIO_AMR_NB)) {
+            return; // skip
+        }
+
         playVideoTest("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
                 + "&itag=13&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
                 + "&sparams=ip,ipbits,expire,id,itag,source"
@@ -70,6 +77,10 @@
                 + "&key=ik0&user=android-device-test", 176, 144);
     }
     public void testHTTP_H263_AMR_Video2() throws Exception {
+        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_H263, MediaFormat.MIMETYPE_AUDIO_AMR_NB)) {
+            return; // skip
+        }
+
         playVideoTest("http://redirector.c.youtube.com/videoplayback?id=c80658495af60617"
                 + "&itag=13&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
                 + "&sparams=ip,ipbits,expire,id,itag,source"
@@ -79,6 +90,10 @@
     }
 
     public void testHTTP_MPEG4SP_AAC_Video1() throws Exception {
+        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
+            return; // skip
+        }
+
         playVideoTest("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
                 + "&itag=17&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
                 + "&sparams=ip,ipbits,expire,id,itag,source"
@@ -87,6 +102,10 @@
                 + "&key=ik0&user=android-device-test", 176, 144);
     }
     public void testHTTP_MPEG4SP_AAC_Video2() throws Exception {
+        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
+            return; // skip
+        }
+
         playVideoTest("http://redirector.c.youtube.com/videoplayback?id=c80658495af60617"
                 + "&itag=17&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
                 + "&sparams=ip,ipbits,expire,id,itag,source"
@@ -96,6 +115,10 @@
     }
 
     public void testHTTP_H264Base_AAC_Video1() throws Exception {
+        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
+            return; // skip
+        }
+
         playVideoTest("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
                 + "&itag=18&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
                 + "&sparams=ip,ipbits,expire,id,itag,source"
@@ -104,6 +127,10 @@
                 + "&key=ik0&user=android-device-test", 640, 360);
     }
     public void testHTTP_H264Base_AAC_Video2() throws Exception {
+        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
+            return; // skip
+        }
+
         playVideoTest("http://redirector.c.youtube.com/videoplayback?id=c80658495af60617"
                 + "&itag=18&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
                 + "&sparams=ip,ipbits,expire,id,itag,source"
@@ -114,6 +141,10 @@
 
     // Streaming HLS video from YouTube
     public void testHLS() throws Exception {
+        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
+            return; // skip
+        }
+
         // Play stream for 60 seconds
         playLiveVideoTest("http://www.youtube.com/api/manifest/hls_variant/id/"
                 + "0168724d02bd9945/itag/5/source/youtube/playlist_type/DVR/ip/"
@@ -165,6 +196,10 @@
                 stream_url = stream_url + "?" + CtsTestServer.NOLENGTH_POSTFIX;
             }
 
+            if (!MediaUtils.checkCodecsForPath(mContext, stream_url)) {
+                return; // skip
+            }
+
             mMediaPlayer.setDataSource(stream_url);
 
             mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
@@ -252,14 +287,23 @@
     }
 
     public void testPlayHlsStream() throws Throwable {
+        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
+            return; // skip
+        }
         localHlsTest("hls.m3u8", false, false);
     }
 
     public void testPlayHlsStreamWithQueryString() throws Throwable {
+        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
+            return; // skip
+        }
         localHlsTest("hls.m3u8", true, false);
     }
 
     public void testPlayHlsStreamWithRedirect() throws Throwable {
+        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
+            return; // skip
+        }
         localHlsTest("hls.m3u8", false, true);
     }
 
diff --git a/tests/tests/media/src/android/media/cts/Vp8CodecTestBase.java b/tests/tests/media/src/android/media/cts/Vp8CodecTestBase.java
index 586b9ef..133b91d 100644
--- a/tests/tests/media/src/android/media/cts/Vp8CodecTestBase.java
+++ b/tests/tests/media/src/android/media/cts/Vp8CodecTestBase.java
@@ -53,10 +53,8 @@
 public class Vp8CodecTestBase extends AndroidTestCase {
 
     protected static final String TAG = "VP8CodecTestBase";
-    protected static final String VP8_MIME = "video/x-vnd.on2.vp8";
-    private static final String VPX_SW_DECODER_NAME = "OMX.google.vp8.decoder";
-    private static final String VPX_SW_ENCODER_NAME = "OMX.google.vp8.encoder";
-    private static final String OMX_SW_CODEC_PREFIX = "OMX.google";
+    protected static final String VP8_MIME = MediaFormat.MIMETYPE_VIDEO_VP8;
+    private static final String GOOGLE_CODEC_PREFIX = "omx.google.";
     protected static final String SDCARD_DIR =
             Environment.getExternalStorageDirectory().getAbsolutePath();
 
@@ -93,29 +91,6 @@
     }
 
     /**
-     * Returns the first codec capable of encoding the specified MIME type, or null if no
-     * match was found.
-     */
-    protected static MediaCodecInfo selectCodec(String mimeType) {
-        int numCodecs = MediaCodecList.getCodecCount();
-        for (int i = 0; i < numCodecs; i++) {
-            MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
-
-            if (!codecInfo.isEncoder()) {
-                continue;
-            }
-
-            String[] types = codecInfo.getSupportedTypes();
-            for (int j = 0; j < types.length; j++) {
-                if (types[j].equalsIgnoreCase(mimeType)) {
-                    return codecInfo;
-                }
-            }
-        }
-        return null;
-    }
-
-    /**
      *  VP8 codec properties generated by getVp8CodecProperties() function.
      */
     private class CodecProperties {
@@ -123,8 +98,8 @@
             this.codecName = codecName;
             this.colorFormat = colorFormat;
         }
-        public boolean  isGoogleSwCodec() {
-            return codecName.startsWith(OMX_SW_CODEC_PREFIX);
+        public boolean  isGoogleCodec() {
+            return codecName.toLowerCase().startsWith(GOOGLE_CODEC_PREFIX);
         }
 
         public final String codecName; // OpenMax component name for VP8 codec.
@@ -135,76 +110,75 @@
      * Function to find VP8 codec.
      *
      * Iterates through the list of available codecs and tries to find
-     * VP8 codec, which can support either YUV420 planar or NV12 color formats.
-     * If forceSwGoogleCodec parameter set to true the function always returns
-     * Google sw VP8 codec.
-     * If forceSwGoogleCodec parameter set to false the functions looks for platform
-     * specific VP8 codec first. If no platform specific codec exist, falls back to
-     * Google sw VP8 codec.
+     * VPX codec, which can support either YUV420 planar or NV12 color formats.
+     * If forceGoogleCodec parameter set to true the function always returns
+     * Google VPX codec.
+     * If forceGoogleCodec parameter set to false the functions looks for platform
+     * specific VPX codec first. If no platform specific codec exist, falls back to
+     * Google VPX codec.
      *
      * @param isEncoder     Flag if encoder is requested.
-     * @param forceSwGoogleCodec  Forces to use Google sw codec.
+     * @param forceGoogleCodec  Forces to use Google codec.
      */
-    private CodecProperties getVp8CodecProperties(boolean isEncoder,
-            boolean forceSwGoogleCodec) throws Exception {
+    private CodecProperties getVpxCodecProperties(
+            boolean isEncoder,
+            MediaFormat format,
+            boolean forceGoogleCodec) throws Exception {
         CodecProperties codecProperties = null;
+        String mime = format.getString(MediaFormat.KEY_MIME);
 
-        if (!forceSwGoogleCodec) {
-            // Loop through the list of omx components in case platform specific codec
-            // is requested.
-            for (int i = 0; i < MediaCodecList.getCodecCount(); i++) {
-                MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
-                if (isEncoder != codecInfo.isEncoder()) {
+        // Loop through the list of omx components in case platform specific codec
+        // is requested.
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        for (MediaCodecInfo codecInfo : mcl.getCodecInfos()) {
+            if (isEncoder != codecInfo.isEncoder()) {
+                continue;
+            }
+            Log.v(TAG, codecInfo.getName());
+            // TODO: remove dependence of Google from the test
+            // Check if this is Google codec - we should ignore it.
+            boolean isGoogleCodec =
+                codecInfo.getName().toLowerCase().startsWith(GOOGLE_CODEC_PREFIX);
+            if (!isGoogleCodec && forceGoogleCodec) {
+                continue;
+            }
+
+            for (String type : codecInfo.getSupportedTypes()) {
+                if (!type.equalsIgnoreCase(mime)) {
                     continue;
                 }
-                Log.v(TAG, codecInfo.getName());
-                // Check if this is sw Google codec - we should ignore it.
-                boolean isGoogleSwCodec = codecInfo.getName().startsWith(OMX_SW_CODEC_PREFIX);
-                if (isGoogleSwCodec) {
+                CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(type);
+                if (!capabilities.isFormatSupported(format)) {
                     continue;
                 }
 
-                for (String type : codecInfo.getSupportedTypes()) {
-                    if (!type.equalsIgnoreCase(VP8_MIME)) {
-                        continue;
-                    }
-                    CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(VP8_MIME);
+                // Get candidate codec properties.
+                Log.v(TAG, "Found candidate codec " + codecInfo.getName());
+                for (int colorFormat: capabilities.colorFormats) {
+                    Log.v(TAG, "   Color: 0x" + Integer.toHexString(colorFormat));
+                }
 
-                    // Get candidate codec properties.
-                    Log.v(TAG, "Found candidate codec " + codecInfo.getName());
-                    for (int colorFormat : capabilities.colorFormats) {
-                        Log.v(TAG, "   Color: 0x" + Integer.toHexString(colorFormat));
-                    }
-
-                    // Check supported color formats.
-                    for (int supportedColorFormat : mSupportedColorList) {
-                        for (int codecColorFormat : capabilities.colorFormats) {
-                            if (codecColorFormat == supportedColorFormat) {
-                                codecProperties = new CodecProperties(codecInfo.getName(),
-                                        codecColorFormat);
-                                Log.v(TAG, "Found target codec " + codecProperties.codecName +
-                                        ". Color: 0x" + Integer.toHexString(codecColorFormat));
+                // Check supported color formats.
+                for (int supportedColorFormat : mSupportedColorList) {
+                    for (int codecColorFormat : capabilities.colorFormats) {
+                        if (codecColorFormat == supportedColorFormat) {
+                            codecProperties = new CodecProperties(codecInfo.getName(),
+                                    codecColorFormat);
+                            Log.v(TAG, "Found target codec " + codecProperties.codecName +
+                                    ". Color: 0x" + Integer.toHexString(codecColorFormat));
+                            // return first HW codec found
+                            if (!isGoogleCodec) {
                                 return codecProperties;
                             }
                         }
                     }
-                    // HW codec we found does not support one of necessary color formats.
-                    throw new RuntimeException("No hw codec with YUV420 or NV12 color formats");
                 }
             }
         }
-        // If no hw vp8 codec exist or sw codec is requested use default Google sw codec.
         if (codecProperties == null) {
-            Log.v(TAG, "Use SW VP8 codec");
-            if (isEncoder) {
-                codecProperties = new CodecProperties(VPX_SW_ENCODER_NAME,
-                        CodecCapabilities.COLOR_FormatYUV420Planar);
-            } else {
-                codecProperties = new CodecProperties(VPX_SW_DECODER_NAME,
-                        CodecCapabilities.COLOR_FormatYUV420Planar);
-            }
+            Log.i(TAG, "no suitable " + (forceGoogleCodec ? "google " : "")
+                    + (isEncoder ? "encoder " : "decoder ") + "found for " + format);
         }
-
         return codecProperties;
     }
 
@@ -223,8 +197,8 @@
         int inputResourceId;
         // Name of the IVF file to write encoded bitsream
         public String outputIvfFilename;
-        // Force to use Google SW VP8 encoder.
-        boolean forceSwEncoder;
+        // Force to use Google VP8 encoder.
+        boolean forceGoogleEncoder;
         // Number of frames to encode.
         int frameCount;
         // Frame rate of input file in frames per second.
@@ -286,7 +260,7 @@
             params.inputResourceId = R.raw.football_qvga;
             params.outputIvfFilename = SDCARD_DIR + File.separator +
                     outputIvfBaseName + resolutionScales[i] + ".ivf";
-            params.forceSwEncoder = false;
+            params.forceGoogleEncoder = false;
             params.frameCount = encodeSeconds * frameRate;
             params.frameRate = frameRate;
             params.frameWidth = Math.min(frameWidth * resolutionScales[i], 1280);
@@ -532,15 +506,15 @@
      * @param inputIvfFilename  The name of the IVF file containing encoded bitsream.
      * @param outputYuvFilename The name of the output YUV file (optional).
      * @param frameRate         Frame rate of input file in frames per second
-     * @param forceSwDecoder    Force to use Googlw sw VP8 decoder.
+     * @param forceGoogleDecoder    Force to use Google VP8 decoder.
      */
     protected ArrayList<MediaCodec.BufferInfo> decode(
             String inputIvfFilename,
             String outputYuvFilename,
             int frameRate,
-            boolean forceSwDecoder) throws Exception {
+            boolean forceGoogleDecoder) throws Exception {
         ArrayList<MediaCodec.BufferInfo> bufferInfos = new ArrayList<MediaCodec.BufferInfo>();
-        CodecProperties properties = getVp8CodecProperties(false, forceSwDecoder);
+
         // Open input/output.
         IvfReader ivf = new IvfReader(inputIvfFilename);
         int frameWidth = ivf.getWidth();
@@ -548,21 +522,27 @@
         int frameCount = ivf.getFrameCount();
         int frameStride = frameWidth;
         int frameSliceHeight = frameHeight;
-        int frameColorFormat = properties.colorFormat;
         assertTrue(frameWidth > 0);
         assertTrue(frameHeight > 0);
         assertTrue(frameCount > 0);
 
+        // Create decoder.
+        MediaFormat format = MediaFormat.createVideoFormat(
+                VP8_MIME, ivf.getWidth(), ivf.getHeight());
+        CodecProperties properties = getVpxCodecProperties(
+                false /* encoder */, format, forceGoogleDecoder);
+        if (properties == null) {
+            ivf.close();
+            return null;
+        }
+        int frameColorFormat = properties.colorFormat;
+        format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
+
         FileOutputStream yuv = null;
         if (outputYuvFilename != null) {
             yuv = new FileOutputStream(outputYuvFilename, false);
         }
 
-        // Create decoder.
-        MediaFormat format = MediaFormat.createVideoFormat(VP8_MIME,
-                                                           ivf.getWidth(),
-                                                           ivf.getHeight());
-        format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
         Log.d(TAG, "Creating decoder " + properties.codecName +
                 ". Color format: 0x" + Integer.toHexString(frameColorFormat) +
                 ". " + frameWidth + " x " + frameHeight);
@@ -1282,11 +1262,20 @@
             EncoderOutputStreamParameters streamParams) throws Exception {
 
         ArrayList<MediaCodec.BufferInfo> bufferInfos = new ArrayList<MediaCodec.BufferInfo>();
-        CodecProperties properties = getVp8CodecProperties(true, streamParams.forceSwEncoder);
-        Log.d(TAG, "Source reslution: " + streamParams.frameWidth + " x " +
+        Log.d(TAG, "Source resolution: "+streamParams.frameWidth + " x " +
                 streamParams.frameHeight);
         int bitrate = streamParams.bitrateSet[0];
 
+        // Create minimal media format signifying desired output.
+        MediaFormat format = MediaFormat.createVideoFormat(
+                VP8_MIME, streamParams.frameWidth, streamParams.frameHeight);
+        format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
+        CodecProperties properties = getVpxCodecProperties(
+                true, format, streamParams.forceGoogleEncoder);
+        if (properties == null) {
+            return null;
+        }
+
         // Open input/output
         InputStream yuvStream = OpenFileOrResourceId(
                 streamParams.inputYuvFilename, streamParams.inputResourceId);
@@ -1294,9 +1283,6 @@
                 streamParams.outputIvfFilename, streamParams.frameWidth, streamParams.frameHeight);
 
         // Create a media format signifying desired output.
-        MediaFormat format = MediaFormat.createVideoFormat(
-                VP8_MIME, streamParams.frameWidth, streamParams.frameHeight);
-        format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
         if (streamParams.bitrateType == VIDEO_ControlRateConstant) {
             format.setInteger("bitrate-mode", VIDEO_ControlRateConstant); // set CBR
         }
@@ -1451,19 +1437,25 @@
         }
 
         ArrayList<MediaCodec.BufferInfo> bufferInfos = new ArrayList<MediaCodec.BufferInfo>();
-        CodecProperties properties = getVp8CodecProperties(true, streamParams.forceSwEncoder);
-        Log.d(TAG, "Source reslution: " + streamParams.frameWidth + " x " +
+        Log.d(TAG, "Source resolution: "+streamParams.frameWidth + " x " +
                 streamParams.frameHeight);
         int bitrate = streamParams.bitrateSet[0];
 
+        // Create minimal media format signifying desired output.
+        MediaFormat format = MediaFormat.createVideoFormat(
+                VP8_MIME, streamParams.frameWidth, streamParams.frameHeight);
+        format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
+        CodecProperties properties = getVpxCodecProperties(
+                true, format, streamParams.forceGoogleEncoder);
+        if (properties == null) {
+            return null;
+        }
+
         // Open input/output
         IvfWriter ivf = new IvfWriter(
                 streamParams.outputIvfFilename, streamParams.frameWidth, streamParams.frameHeight);
 
         // Create a media format signifying desired output.
-        MediaFormat format = MediaFormat.createVideoFormat(
-                VP8_MIME, streamParams.frameWidth, streamParams.frameHeight);
-        format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
         if (streamParams.bitrateType == VIDEO_ControlRateConstant) {
             format.setInteger("bitrate-mode", VIDEO_ControlRateConstant); // set CBR
         }
@@ -1546,9 +1538,22 @@
         boolean bufferConsumedTotal = false;
         CodecProperties[] codecProperties = new CodecProperties[numEncoders];
 
-        for (int i = 0; i < numEncoders; i++) {
-            EncoderOutputStreamParameters params = encodingParams.get(i);
-            CodecProperties properties = getVp8CodecProperties(true, params.forceSwEncoder);
+        numEncoders = 0;
+        for (EncoderOutputStreamParameters params : encodingParams) {
+            int i = numEncoders;
+            Log.d(TAG, "Source resolution: " + params.frameWidth + " x " +
+                    params.frameHeight);
+            int bitrate = params.bitrateSet[0];
+
+            // Create minimal media format signifying desired output.
+            format[i] = MediaFormat.createVideoFormat(VP8_MIME,
+                    params.frameWidth, params.frameHeight);
+            format[i].setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
+            CodecProperties properties = getVpxCodecProperties(
+                    true, format[i], params.forceGoogleEncoder);
+            if (properties == null) {
+                continue;
+            }
 
             // Check if scaled image was created
             int scale = params.frameWidth / srcFrameWidth;
@@ -1574,10 +1579,6 @@
             srcFrame[i] = new byte[frameSize];
 
             // Create a media format signifying desired output.
-            int bitrate = params.bitrateSet[0];
-            format[i] = MediaFormat.createVideoFormat(VP8_MIME,
-                    params.frameWidth, params.frameHeight);
-            format[i].setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
             if (params.bitrateType == VIDEO_ControlRateConstant) {
                 format[i].setInteger("bitrate-mode", VIDEO_ControlRateConstant); // set CBR
             }
@@ -1607,6 +1608,11 @@
             codecProperties[i] = new CodecProperties(properties.codecName, properties.colorFormat);
 
             inputConsumed[i] = true;
+            ++numEncoders;
+        }
+        if (numEncoders == 0) {
+            Log.i(TAG, "no suitable encoders found for any of the streams");
+            return null;
         }
 
         while (!sawOutputEOSTotal) {
@@ -1644,7 +1650,8 @@
                     // Convert YUV420 to NV12 if necessary
                     if (codecProperties[i].colorFormat !=
                             CodecCapabilities.COLOR_FormatYUV420Planar) {
-                        srcFrame[i] = YUV420ToNV(params.frameWidth, params.frameHeight, srcFrame[i]);
+                        srcFrame[i] =
+                            YUV420ToNV(params.frameWidth, params.frameHeight, srcFrame[i]);
                     }
                 }
 
diff --git a/tests/tests/media/src/android/media/cts/Vp8EncoderTest.java b/tests/tests/media/src/android/media/cts/Vp8EncoderTest.java
index 57e397f..be7e721 100644
--- a/tests/tests/media/src/android/media/cts/Vp8EncoderTest.java
+++ b/tests/tests/media/src/android/media/cts/Vp8EncoderTest.java
@@ -18,6 +18,8 @@
 
 import android.media.MediaCodec;
 import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
+import android.media.MediaFormat;
 import android.util.Log;
 import com.android.cts.media.R;
 
@@ -52,13 +54,13 @@
     private static final int[] TEST_BITRATES_SET = { 300000, 500000, 700000, 900000 };
     // Maximum allowed bitrate variation from the target value.
     private static final double MAX_BITRATE_VARIATION = 0.2;
-    // Average PSNR values for reference SW VP8 codec for the above bitrates.
+    // Average PSNR values for reference Google VP8 codec for the above bitrates.
     private static final double[] REFERENCE_AVERAGE_PSNR = { 33.1, 35.2, 36.6, 37.8 };
-    // Minimum PSNR values for reference SW VP8 codec for the above bitrates.
+    // Minimum PSNR values for reference Google VP8 codec for the above bitrates.
     private static final double[] REFERENCE_MINIMUM_PSNR = { 25.9, 27.5, 28.4, 30.3 };
-    // Maximum allowed average PSNR difference of HW encoder comparing to reference SW encoder.
+    // Maximum allowed average PSNR difference of encoder comparing to reference Google encoder.
     private static final double MAX_AVERAGE_PSNR_DIFFERENCE = 2;
-    // Maximum allowed minimum PSNR difference of HW encoder comparing to reference SW encoder.
+    // Maximum allowed minimum PSNR difference of encoder comparing to reference Google encoder.
     private static final double MAX_MINIMUM_PSNR_DIFFERENCE = 4;
     // Maximum allowed average PSNR difference of the encoder running in a looper thread with 0 ms
     // buffer dequeue timeout comparing to the encoder running in a callee's thread with 100 ms
@@ -80,13 +82,8 @@
      * Also checks the average bitrate is within MAX_BITRATE_VARIATION of the target value.
      */
     public void testBasic() throws Exception {
-        MediaCodecInfo codecInfo = selectCodec(VP8_MIME);
-        if (codecInfo == null) {
-            Log.w(TAG, "Codec " + VP8_MIME + " not supported. Return from testBasic.");
-            return;
-        }
-
         int encodeSeconds = 9;
+        boolean skipped = true;
 
         for (int targetBitrate : TEST_BITRATES_SET) {
             EncoderOutputStreamParameters params = getDefaultEncodingParameters(
@@ -100,6 +97,11 @@
                     targetBitrate,
                     true);
             ArrayList<MediaCodec.BufferInfo> bufInfo = encode(params);
+            if (bufInfo == null) {
+                continue;
+            }
+            skipped = false;
+
             Vp8EncodingStatistics statistics = computeEncodingStatistics(bufInfo);
 
             assertEquals("Stream bitrate " + statistics.mAverageBitrate +
@@ -107,7 +109,11 @@
                     targetBitrate, statistics.mAverageBitrate,
                     MAX_BITRATE_VARIATION * targetBitrate);
 
-            decode(params.outputIvfFilename, null, FPS, params.forceSwEncoder);
+            decode(params.outputIvfFilename, null, FPS, params.forceGoogleEncoder);
+        }
+
+        if (skipped) {
+            Log.i(TAG, "SKIPPING testBasic(): codec is not supported");
         }
     }
 
@@ -119,12 +125,6 @@
      * does not change much for two different ways of the encoder call.
      */
     public void testAsyncEncoding() throws Exception {
-        MediaCodecInfo codecInfo = selectCodec(VP8_MIME);
-        if (codecInfo == null) {
-            Log.w(TAG, "Codec " + VP8_MIME + " not supported. Return from testAsyncEncoding.");
-            return;
-        }
-
         int encodeSeconds = 9;
 
         // First test the encoder running in a looper thread with buffer callbacks enabled.
@@ -140,8 +140,12 @@
                 BITRATE,
                 syncEncoding);
         ArrayList<MediaCodec.BufferInfo> bufInfos = encodeAsync(params);
+        if (bufInfos == null) {
+            Log.i(TAG, "SKIPPING testAsyncEncoding(): no suitable encoder found");
+            return;
+        }
         computeEncodingStatistics(bufInfos);
-        decode(params.outputIvfFilename, OUTPUT_YUV, FPS, params.forceSwEncoder);
+        decode(params.outputIvfFilename, OUTPUT_YUV, FPS, params.forceGoogleEncoder);
         Vp8DecodingStatistics statisticsAsync = computeDecodingStatistics(
                 params.inputYuvFilename, R.raw.football_qvga, OUTPUT_YUV,
                 params.frameWidth, params.frameHeight);
@@ -160,8 +164,12 @@
                 BITRATE,
                 syncEncoding);
         bufInfos = encode(params);
+        if (bufInfos == null) {
+            Log.i(TAG, "SKIPPING testAsyncEncoding(): no suitable encoder found");
+            return;
+        }
         computeEncodingStatistics(bufInfos);
-        decode(params.outputIvfFilename, OUTPUT_YUV, FPS, params.forceSwEncoder);
+        decode(params.outputIvfFilename, OUTPUT_YUV, FPS, params.forceGoogleEncoder);
         Vp8DecodingStatistics statisticsSync = computeDecodingStatistics(
                 params.inputYuvFilename, R.raw.football_qvga, OUTPUT_YUV,
                 params.frameWidth, params.frameHeight);
@@ -186,12 +194,6 @@
      * The test does not verify the output stream.
      */
     public void testSyncFrame() throws Exception {
-        MediaCodecInfo codecInfo = selectCodec(VP8_MIME);
-        if (codecInfo == null) {
-            Log.w(TAG, "Codec " + VP8_MIME + " not supported. Return from testSyncFrame.");
-            return;
-        }
-
         int encodeSeconds = 9;
 
         EncoderOutputStreamParameters params = getDefaultEncodingParameters(
@@ -207,6 +209,11 @@
         params.syncFrameInterval = encodeSeconds * FPS;
         params.syncForceFrameInterval = FPS;
         ArrayList<MediaCodec.BufferInfo> bufInfo = encode(params);
+        if (bufInfo == null) {
+            Log.i(TAG, "SKIPPING testSyncFrame(): no suitable encoder found");
+            return;
+        }
+
         Vp8EncodingStatistics statistics = computeEncodingStatistics(bufInfo);
 
         // First check if we got expected number of key frames.
@@ -236,12 +243,6 @@
      * bitrate after 6 seconds and ensure the encoder responds.
      */
     public void testDynamicBitrateChange() throws Exception {
-        MediaCodecInfo codecInfo = selectCodec(VP8_MIME);
-        if (codecInfo == null) {
-            Log.w(TAG, "Codec " + VP8_MIME + " not supported. Return from testDynamicBitrateChange.");
-            return;
-        }
-
         int encodeSeconds = 12;    // Encoding sequence duration in seconds.
         int[] bitrateTargetValues = { 400000, 800000 };  // List of bitrates to test.
 
@@ -268,6 +269,11 @@
         }
 
         ArrayList<MediaCodec.BufferInfo> bufInfo = encode(params);
+        if (bufInfo == null) {
+            Log.i(TAG, "SKIPPING testDynamicBitrateChange(): no suitable encoder found");
+            return;
+        }
+
         Vp8EncodingStatistics statistics = computeEncodingStatistics(bufInfo);
 
         // Calculate actual average bitrates  for every [stepSeconds] second.
@@ -304,10 +310,12 @@
       * Compares average bitrate and PSNR for sequential and parallel runs.
       */
      public void testParallelEncodingAndDecoding() throws Exception {
-         MediaCodecInfo codecInfo = selectCodec(VP8_MIME);
-         if (codecInfo == null) {
-             Log.w(TAG, "Codec " + VP8_MIME + " not supported. "
-                     + "Return from testParallelEncodingAndDecoding.");
+         // check for encoder up front, as by the time we detect lack of
+         // encoder support, we may have already started decoding.
+         MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+         MediaFormat format = MediaFormat.createVideoFormat(VP8_MIME, WIDTH, HEIGHT);
+         if (mcl.findEncoderForFormat(format) == null) {
+             Log.i(TAG, "SKIPPING testParallelEncodingAndDecoding(): no suitable encoder found");
              return;
          }
 
@@ -343,7 +351,7 @@
          Runnable runDecoder = new Runnable() {
              public void run() {
                  try {
-                     decode(inputIvfFilename, OUTPUT_YUV, FPS, params.forceSwEncoder);
+                     decode(inputIvfFilename, OUTPUT_YUV, FPS, params.forceGoogleEncoder);
                      Vp8DecodingStatistics statistics = computeDecodingStatistics(
                             params.inputYuvFilename, R.raw.football_qvga, OUTPUT_YUV,
                             params.frameWidth, params.frameHeight);
@@ -400,21 +408,17 @@
      * Run the the encoder for 9 seconds for each bitrate and calculate PSNR
      * for each encoded stream.
      * Video streams with higher bitrates should have higher PSNRs.
-     * Also compares average and minimum PSNR of HW codec with PSNR values of reference SW codec.
+     * Also compares average and minimum PSNR of codec with PSNR values of reference Google codec.
      */
     public void testEncoderQuality() throws Exception {
-        MediaCodecInfo codecInfo = selectCodec(VP8_MIME);
-        if (codecInfo == null) {
-            Log.w(TAG, "Codec " + VP8_MIME + " not supported. Return from testEncoderQuality.");
-            return;
-        }
-
         int encodeSeconds = 9;      // Encoding sequence duration in seconds for each bitrate.
         double[] psnrPlatformCodecAverage = new double[TEST_BITRATES_SET.length];
         double[] psnrPlatformCodecMin = new double[TEST_BITRATES_SET.length];
+        boolean[] completed = new boolean[TEST_BITRATES_SET.length];
+        boolean skipped = true;
 
         // Run platform specific encoder for different bitrates
-        // and compare PSNR of hw codec with PSNR of reference sw codec.
+        // and compare PSNR of codec with PSNR of reference Google codec.
         for (int i = 0; i < TEST_BITRATES_SET.length; i++) {
             EncoderOutputStreamParameters params = getDefaultEncodingParameters(
                     INPUT_YUV,
@@ -426,9 +430,15 @@
                     BITRATE_MODE,
                     TEST_BITRATES_SET[i],
                     true);
-            encode(params);
+            if (encode(params) == null) {
+                // parameters not supported, try other bitrates
+                completed[i] = false;
+                continue;
+            }
+            completed[i] = true;
+            skipped = false;
 
-            decode(params.outputIvfFilename, OUTPUT_YUV, FPS, params.forceSwEncoder);
+            decode(params.outputIvfFilename, OUTPUT_YUV, FPS, params.forceGoogleEncoder);
             Vp8DecodingStatistics statistics = computeDecodingStatistics(
                     params.inputYuvFilename, R.raw.football_qvga, OUTPUT_YUV,
                     params.frameWidth, params.frameHeight);
@@ -436,9 +446,20 @@
             psnrPlatformCodecMin[i] = statistics.mMinimumPSNR;
         }
 
+        if (skipped) {
+            Log.i(TAG, "SKIPPING testEncoderQuality(): no bitrates supported");
+            return;
+        }
+
         // First do a sanity check - higher bitrates should results in higher PSNR.
         for (int i = 1; i < TEST_BITRATES_SET.length ; i++) {
+            if (!completed[i]) {
+                continue;
+            }
             for (int j = 0; j < i; j++) {
+                if (!completed[j]) {
+                    continue;
+                }
                 double differenceBitrate = TEST_BITRATES_SET[i] - TEST_BITRATES_SET[j];
                 double differencePSNR = psnrPlatformCodecAverage[i] - psnrPlatformCodecAverage[j];
                 if (differenceBitrate * differencePSNR < 0) {
@@ -450,12 +471,16 @@
             }
         }
 
-        // Then compare average and minimum PSNR of platform codec with reference sw codec -
+        // Then compare average and minimum PSNR of platform codec with reference Google codec -
         // average PSNR for platform codec should be no more than 2 dB less than reference PSNR
         // and minumum PSNR - no more than 4 dB less than reference minimum PSNR.
         // These PSNR difference numbers are arbitrary for now, will need further estimation
-        // when more devices with hw VP8 codec will appear.
+        // when more devices with HW VP8 codec will appear.
         for (int i = 0; i < TEST_BITRATES_SET.length ; i++) {
+            if (!completed[i]) {
+                continue;
+            }
+
             Log.d(TAG, "Bitrate " + TEST_BITRATES_SET[i]);
             Log.d(TAG, "Reference: Average: " + REFERENCE_AVERAGE_PSNR[i] + ". Minimum: " +
                     REFERENCE_MINIMUM_PSNR[i]);
@@ -470,7 +495,7 @@
             if (psnrPlatformCodecMin[i] < REFERENCE_MINIMUM_PSNR[i] -
                     MAX_MINIMUM_PSNR_DIFFERENCE) {
                 throw new RuntimeException("Low minimum PSNR " + psnrPlatformCodecMin[i] +
-                        " comparing to sw PSNR " + REFERENCE_MINIMUM_PSNR[i] +
+                        " comparing to reference PSNR " + REFERENCE_MINIMUM_PSNR[i] +
                         " for bitrate " + TEST_BITRATES_SET[i]);
             }
         }
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/H264R1080pAacLongPlayerTest.java b/tests/tests/mediastress/src/android/mediastress/cts/H264R1080pAacLongPlayerTest.java
index b7f7564..7216871 100644
--- a/tests/tests/mediastress/src/android/mediastress/cts/H264R1080pAacLongPlayerTest.java
+++ b/tests/tests/mediastress/src/android/mediastress/cts/H264R1080pAacLongPlayerTest.java
@@ -16,20 +16,12 @@
 
 package android.mediastress.cts;
 
-import android.media.CamcorderProfile;
-import android.media.MediaRecorder.AudioEncoder;
-import android.media.MediaRecorder.VideoEncoder;
-
 public class H264R1080pAacLongPlayerTest extends MediaPlayerStressTest {
     private static final String VIDEO_PATH_MIDDLE = "bbb_full/1920x1080/mp4_libx264_libfaac/";
     private final String[] mMedias = {
         "bbb_full.ffmpeg.1920x1080.mp4.libx264_10000kbps_30fps.libfaac_stereo_192kbps_48000Hz.mp4"
     };
 
-    public H264R1080pAacLongPlayerTest() {
-        super(CamcorderProfile.QUALITY_1080P, VideoEncoder.H264, AudioEncoder.AAC);
-    }
-
     public void testPlay00() throws Exception {
         doTestVideoPlaybackLong(0);
     }
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/H264R1080pAacRepeatedPlayerTest.java b/tests/tests/mediastress/src/android/mediastress/cts/H264R1080pAacRepeatedPlayerTest.java
index db5d732..28e1158 100644
--- a/tests/tests/mediastress/src/android/mediastress/cts/H264R1080pAacRepeatedPlayerTest.java
+++ b/tests/tests/mediastress/src/android/mediastress/cts/H264R1080pAacRepeatedPlayerTest.java
@@ -16,20 +16,12 @@
 
 package android.mediastress.cts;
 
-import android.media.CamcorderProfile;
-import android.media.MediaRecorder.AudioEncoder;
-import android.media.MediaRecorder.VideoEncoder;
-
 public class H264R1080pAacRepeatedPlayerTest extends MediaPlayerStressTest {
     private static final String VIDEO_PATH_MIDDLE = "bbb_short/1920x1080/mp4_libx264_libfaac/";
     private final String[] mMedias = {
         "bbb_short.ffmpeg.1920x1080.mp4.libx264_10000kbps_30fps.libfaac_stereo_192kbps_48000Hz.mp4",
     };
 
-    public H264R1080pAacRepeatedPlayerTest() {
-        super(CamcorderProfile.QUALITY_1080P, VideoEncoder.H264, AudioEncoder.AAC);
-    }
-
     public void testPlay00() throws Exception {
         doTestVideoPlaybackRepeated(0);
     }
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/H264R1080pAacShortPlayerTest.java b/tests/tests/mediastress/src/android/mediastress/cts/H264R1080pAacShortPlayerTest.java
index 3b7da57..2e7d7ec 100644
--- a/tests/tests/mediastress/src/android/mediastress/cts/H264R1080pAacShortPlayerTest.java
+++ b/tests/tests/mediastress/src/android/mediastress/cts/H264R1080pAacShortPlayerTest.java
@@ -16,10 +16,6 @@
 
 package android.mediastress.cts;
 
-import android.media.CamcorderProfile;
-import android.media.MediaRecorder.AudioEncoder;
-import android.media.MediaRecorder.VideoEncoder;
-
 public class H264R1080pAacShortPlayerTest extends MediaPlayerStressTest {
     private static final String VIDEO_PATH_MIDDLE = "bbb_short/1920x1080/mp4_libx264_libfaac/";
     private final String[] mMedias = {
@@ -28,10 +24,6 @@
         "bbb_short.ffmpeg.1920x1080.mp4.libx264_5000kbps_30fps.libfaac_stereo_192kbps_48000Hz.mp4"
     };
 
-    public H264R1080pAacShortPlayerTest() {
-        super(CamcorderProfile.QUALITY_1080P, VideoEncoder.H264, AudioEncoder.AAC);
-    }
-
     public void testPlay00() throws Exception {
         doTestVideoPlaybackShort(0);
     }
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/H264R480pAacLongPlayerTest.java b/tests/tests/mediastress/src/android/mediastress/cts/H264R480pAacLongPlayerTest.java
index f01aa75..3628d99 100644
--- a/tests/tests/mediastress/src/android/mediastress/cts/H264R480pAacLongPlayerTest.java
+++ b/tests/tests/mediastress/src/android/mediastress/cts/H264R480pAacLongPlayerTest.java
@@ -16,20 +16,12 @@
 
 package android.mediastress.cts;
 
-import android.media.CamcorderProfile;
-import android.media.MediaRecorder.AudioEncoder;
-import android.media.MediaRecorder.VideoEncoder;
-
 public class H264R480pAacLongPlayerTest extends MediaPlayerStressTest {
     private static final String VIDEO_PATH_MIDDLE = "bbb_full/720x480/mp4_libx264_libfaac/";
     private final String[] mMedias = {
         "bbb_full.ffmpeg.720x480.mp4.libx264_500kbps_25fps.libfaac_stereo_128kbps_44100Hz.mp4"
     };
 
-    public H264R480pAacLongPlayerTest() {
-        super(CamcorderProfile.QUALITY_480P, VideoEncoder.H264, AudioEncoder.AAC);
-    }
-
     public void testPlay00() throws Exception {
         doTestVideoPlaybackLong(0);
     }
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/H264R480pAacShortPlayerTest.java b/tests/tests/mediastress/src/android/mediastress/cts/H264R480pAacShortPlayerTest.java
index 81b80e5..ef01327 100644
--- a/tests/tests/mediastress/src/android/mediastress/cts/H264R480pAacShortPlayerTest.java
+++ b/tests/tests/mediastress/src/android/mediastress/cts/H264R480pAacShortPlayerTest.java
@@ -16,10 +16,6 @@
 
 package android.mediastress.cts;
 
-import android.media.CamcorderProfile;
-import android.media.MediaRecorder.AudioEncoder;
-import android.media.MediaRecorder.VideoEncoder;
-
 public class H264R480pAacShortPlayerTest extends MediaPlayerStressTest {
     private static final String VIDEO_PATH_MIDDLE = "bbb_short/720x480/mp4_libx264_libfaac/";
     private final String[] mMedias = {
@@ -37,10 +33,6 @@
         "bbb_short.ffmpeg.720x480.mp4.libx264_500kbps_30fps.libfaac_stereo_192kbps_44100Hz.mp4"
     };
 
-    public H264R480pAacShortPlayerTest() {
-        super(CamcorderProfile.QUALITY_480P, VideoEncoder.H264, AudioEncoder.AAC);
-    }
-
     public void testPlay00() throws Exception {
         doTestVideoPlaybackShort(0);
     }
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/H264R720pAacLongPlayerTest.java b/tests/tests/mediastress/src/android/mediastress/cts/H264R720pAacLongPlayerTest.java
index 3efec62..6a126fa 100644
--- a/tests/tests/mediastress/src/android/mediastress/cts/H264R720pAacLongPlayerTest.java
+++ b/tests/tests/mediastress/src/android/mediastress/cts/H264R720pAacLongPlayerTest.java
@@ -16,10 +16,6 @@
 
 package android.mediastress.cts;
 
-import android.media.CamcorderProfile;
-import android.media.MediaRecorder.AudioEncoder;
-import android.media.MediaRecorder.VideoEncoder;
-
 public class H264R720pAacLongPlayerTest extends MediaPlayerStressTest {
     private static final String VIDEO_PATH_MIDDLE = "bbb_full/1280x720/mp4_libx264_libfaac/";
     private final String[] mMedias = {
@@ -27,10 +23,6 @@
         "bbb_full.ffmpeg.1280x720.mp4.libx264_1750kbps_30fps.libfaac_stereo_192kbps_48000Hz.mp4"
     };
 
-    public H264R720pAacLongPlayerTest() {
-        super(CamcorderProfile.QUALITY_720P, VideoEncoder.H264, AudioEncoder.AAC);
-    }
-
     public void testPlay00() throws Exception {
         doTestVideoPlaybackLong(0);
     }
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/H264R720pAacShortPlayerTest.java b/tests/tests/mediastress/src/android/mediastress/cts/H264R720pAacShortPlayerTest.java
index a6f162d..0fba0f3 100644
--- a/tests/tests/mediastress/src/android/mediastress/cts/H264R720pAacShortPlayerTest.java
+++ b/tests/tests/mediastress/src/android/mediastress/cts/H264R720pAacShortPlayerTest.java
@@ -16,10 +16,6 @@
 
 package android.mediastress.cts;
 
-import android.media.CamcorderProfile;
-import android.media.MediaRecorder.AudioEncoder;
-import android.media.MediaRecorder.VideoEncoder;
-
 public class H264R720pAacShortPlayerTest extends MediaPlayerStressTest {
     private static final String VIDEO_PATH_MIDDLE = "bbb_short/1280x720/mp4_libx264_libfaac/";
     private final String[] mMedias = {
@@ -38,10 +34,6 @@
         "bbb_short.ffmpeg.1280x720.mp4.libx264_500kbps_30fps.libfaac_stereo_192kbps_44100Hz.mp4"
     };
 
-    public H264R720pAacShortPlayerTest() {
-        super(CamcorderProfile.QUALITY_720P, VideoEncoder.H264, AudioEncoder.AAC);
-    }
-
     public void testPlay00() throws Exception {
         doTestVideoPlaybackShort(0);
     }
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/HEVCR1080pAacLongPlayerTest.java b/tests/tests/mediastress/src/android/mediastress/cts/HEVCR1080pAacLongPlayerTest.java
index e8a92e0..f396706 100644
--- a/tests/tests/mediastress/src/android/mediastress/cts/HEVCR1080pAacLongPlayerTest.java
+++ b/tests/tests/mediastress/src/android/mediastress/cts/HEVCR1080pAacLongPlayerTest.java
@@ -16,20 +16,12 @@
 
 package android.mediastress.cts;
 
-import android.media.CamcorderProfile;
-import android.media.MediaRecorder.AudioEncoder;
-import android.media.MediaRecorder.VideoEncoder;
-
 public class HEVCR1080pAacLongPlayerTest extends MediaPlayerStressTest {
     private static final String VIDEO_PATH_MIDDLE = "bbb_full/1920x1080/mp4_libx265_libfaac/";
     private final String[] mMedias = {
         "bbb_full.ffmpeg.1920x1080.mp4.libx265_6500kbps_30fps.libfaac_stereo_128kbps_48000Hz.mp4"
     };
 
-    public HEVCR1080pAacLongPlayerTest() {
-        super(CamcorderProfile.QUALITY_1080P, VideoEncoder.H264, AudioEncoder.AAC);
-    }
-
     public void testPlay00() throws Exception {
         doTestVideoPlaybackLong(0);
     }
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/HEVCR1080pAacRepeatedPlayerTest.java b/tests/tests/mediastress/src/android/mediastress/cts/HEVCR1080pAacRepeatedPlayerTest.java
index 7ce3c3a..879ac6e 100644
--- a/tests/tests/mediastress/src/android/mediastress/cts/HEVCR1080pAacRepeatedPlayerTest.java
+++ b/tests/tests/mediastress/src/android/mediastress/cts/HEVCR1080pAacRepeatedPlayerTest.java
@@ -16,20 +16,12 @@
 
 package android.mediastress.cts;
 
-import android.media.CamcorderProfile;
-import android.media.MediaRecorder.AudioEncoder;
-import android.media.MediaRecorder.VideoEncoder;
-
 public class HEVCR1080pAacRepeatedPlayerTest extends MediaPlayerStressTest {
     private static final String VIDEO_PATH_MIDDLE = "bbb_short/1920x1080/mp4_libx265_libfaac/";
     private final String[] mMedias = {
         "bbb_short.fmpeg.1920x1080.mp4.libx265_6500kbps_30fps.libfaac_stereo_128kbps_48000hz.mp4",
     };
 
-    public HEVCR1080pAacRepeatedPlayerTest() {
-        super(CamcorderProfile.QUALITY_1080P, VideoEncoder.H264, AudioEncoder.AAC);
-    }
-
     public void testPlay00() throws Exception {
         doTestVideoPlaybackRepeated(0);
     }
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/HEVCR1080pAacShortPlayerTest.java b/tests/tests/mediastress/src/android/mediastress/cts/HEVCR1080pAacShortPlayerTest.java
index 1d12b8c..eebe643 100644
--- a/tests/tests/mediastress/src/android/mediastress/cts/HEVCR1080pAacShortPlayerTest.java
+++ b/tests/tests/mediastress/src/android/mediastress/cts/HEVCR1080pAacShortPlayerTest.java
@@ -16,10 +16,6 @@
 
 package android.mediastress.cts;
 
-import android.media.CamcorderProfile;
-import android.media.MediaRecorder.AudioEncoder;
-import android.media.MediaRecorder.VideoEncoder;
-
 public class HEVCR1080pAacShortPlayerTest extends MediaPlayerStressTest {
     private static final String VIDEO_PATH_MIDDLE = "bbb_short/1920x1080/mp4_libx265_libfaac/";
     private final String[] mMedias = {
@@ -28,10 +24,6 @@
         "bbb_short.fmpeg.1920x1080.mp4.libx265_3250kbps_30fps.libfaac_stereo_128kbps_48000hz.mp4"
     };
 
-    public HEVCR1080pAacShortPlayerTest() {
-        super(CamcorderProfile.QUALITY_1080P, VideoEncoder.H264, AudioEncoder.AAC);
-    }
-
     public void testPlay00() throws Exception {
         doTestVideoPlaybackShort(0);
     }
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/HEVCR480pAacLongPlayerTest.java b/tests/tests/mediastress/src/android/mediastress/cts/HEVCR480pAacLongPlayerTest.java
index e54c51f..704a0d0 100644
--- a/tests/tests/mediastress/src/android/mediastress/cts/HEVCR480pAacLongPlayerTest.java
+++ b/tests/tests/mediastress/src/android/mediastress/cts/HEVCR480pAacLongPlayerTest.java
@@ -16,20 +16,12 @@
 
 package android.mediastress.cts;
 
-import android.media.CamcorderProfile;
-import android.media.MediaRecorder.AudioEncoder;
-import android.media.MediaRecorder.VideoEncoder;
-
 public class HEVCR480pAacLongPlayerTest extends MediaPlayerStressTest {
     private static final String VIDEO_PATH_MIDDLE = "bbb_full/720x480/mp4_libx265_libfaac/";
     private final String[] mMedias = {
         "bbb_full.ffmpeg.720x480.mp4.libx265_325kbps_24fps.libfaac_stereo_128kbps_48000Hz.mp4"
     };
 
-    public HEVCR480pAacLongPlayerTest() {
-        super(CamcorderProfile.QUALITY_480P, VideoEncoder.H264, AudioEncoder.AAC);
-    }
-
     public void testPlay00() throws Exception {
         doTestVideoPlaybackLong(0);
     }
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/HEVCR480pAacShortPlayerTest.java b/tests/tests/mediastress/src/android/mediastress/cts/HEVCR480pAacShortPlayerTest.java
index 2b64abd..d1ab245 100644
--- a/tests/tests/mediastress/src/android/mediastress/cts/HEVCR480pAacShortPlayerTest.java
+++ b/tests/tests/mediastress/src/android/mediastress/cts/HEVCR480pAacShortPlayerTest.java
@@ -16,10 +16,6 @@
 
 package android.mediastress.cts;
 
-import android.media.CamcorderProfile;
-import android.media.MediaRecorder.AudioEncoder;
-import android.media.MediaRecorder.VideoEncoder;
-
 public class HEVCR480pAacShortPlayerTest extends MediaPlayerStressTest {
     private static final String VIDEO_PATH_MIDDLE = "bbb_short/720x480/mp4_libx265_libfaac/";
     private final String[] mMedias = {
@@ -31,10 +27,6 @@
         "bbb_short.fmpeg.720x480.mp4.libx265_325kbps_30fps.libfaac_stereo_128kbps_48000hz.mp4"
     };
 
-    public HEVCR480pAacShortPlayerTest() {
-        super(CamcorderProfile.QUALITY_480P, VideoEncoder.H264, AudioEncoder.AAC);
-    }
-
     public void testPlay00() throws Exception {
         doTestVideoPlaybackShort(0);
     }
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/HEVCR720pAacLongPlayerTest.java b/tests/tests/mediastress/src/android/mediastress/cts/HEVCR720pAacLongPlayerTest.java
index 540f78a..bf6b787 100644
--- a/tests/tests/mediastress/src/android/mediastress/cts/HEVCR720pAacLongPlayerTest.java
+++ b/tests/tests/mediastress/src/android/mediastress/cts/HEVCR720pAacLongPlayerTest.java
@@ -16,10 +16,6 @@
 
 package android.mediastress.cts;
 
-import android.media.CamcorderProfile;
-import android.media.MediaRecorder.AudioEncoder;
-import android.media.MediaRecorder.VideoEncoder;
-
 public class HEVCR720pAacLongPlayerTest extends MediaPlayerStressTest {
     private static final String VIDEO_PATH_MIDDLE = "bbb_full/1280x720/mp4_libx265_libfaac/";
     private final String[] mMedias = {
@@ -27,10 +23,6 @@
         "bbb_full.ffmpeg.1280x720.mp4.libx265_1140kbps_30fps.libfaac_stereo_128kbps_48000Hz.mp4"
     };
 
-    public HEVCR720pAacLongPlayerTest() {
-        super(CamcorderProfile.QUALITY_720P, VideoEncoder.H264, AudioEncoder.AAC);
-    }
-
     public void testPlay00() throws Exception {
         doTestVideoPlaybackLong(0);
     }
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/HEVCR720pAacShortPlayerTest.java b/tests/tests/mediastress/src/android/mediastress/cts/HEVCR720pAacShortPlayerTest.java
index dd93dfc..7c42e83 100644
--- a/tests/tests/mediastress/src/android/mediastress/cts/HEVCR720pAacShortPlayerTest.java
+++ b/tests/tests/mediastress/src/android/mediastress/cts/HEVCR720pAacShortPlayerTest.java
@@ -16,10 +16,6 @@
 
 package android.mediastress.cts;
 
-import android.media.CamcorderProfile;
-import android.media.MediaRecorder.AudioEncoder;
-import android.media.MediaRecorder.VideoEncoder;
-
 public class HEVCR720pAacShortPlayerTest extends MediaPlayerStressTest {
     private static final String VIDEO_PATH_MIDDLE = "bbb_short/1280x720/mp4_libx265_libfaac/";
     private final String[] mMedias = {
@@ -34,10 +30,6 @@
         "bbb_short.fmpeg.1280x720.mp4.libx265_6500kbps_30fps.libfaac_stereo_128kbps_48000hz.mp4"
     };
 
-    public HEVCR720pAacShortPlayerTest() {
-        super(CamcorderProfile.QUALITY_720P, VideoEncoder.H264, AudioEncoder.AAC);
-    }
-
     public void testPlay00() throws Exception {
         doTestVideoPlaybackShort(0);
     }
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/MediaFrameworkTest.java b/tests/tests/mediastress/src/android/mediastress/cts/MediaFrameworkTest.java
index 09cc8b8..d39bc16 100644
--- a/tests/tests/mediastress/src/android/mediastress/cts/MediaFrameworkTest.java
+++ b/tests/tests/mediastress/src/android/mediastress/cts/MediaFrameworkTest.java
@@ -19,15 +19,13 @@
 import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
-import android.graphics.Bitmap;
+import android.media.MediaFormat;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.PowerManager;
 import android.util.Log;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
-import android.view.ViewGroup;
-import android.widget.ImageView;
 
 import com.android.cts.mediastress.R;
 
@@ -85,7 +83,7 @@
     }
 
     public void startPlayback(String filename){
-      String mimetype = "audio/mpeg";
+      String mimetype = MediaFormat.MIMETYPE_AUDIO_MPEG;
       Uri path = Uri.parse(filename);
       Intent intent = new Intent(Intent.ACTION_VIEW);
       intent.setDataAndType(path, mimetype);
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/MediaPlayerStressTest.java b/tests/tests/mediastress/src/android/mediastress/cts/MediaPlayerStressTest.java
index d980e52..7c65824 100644
--- a/tests/tests/mediastress/src/android/mediastress/cts/MediaPlayerStressTest.java
+++ b/tests/tests/mediastress/src/android/mediastress/cts/MediaPlayerStressTest.java
@@ -20,6 +20,7 @@
 import android.app.Instrumentation;
 import android.content.Intent;
 import android.media.CamcorderProfile;
+import android.cts.util.MediaUtils;
 import android.media.MediaRecorder.AudioEncoder;
 import android.media.MediaRecorder.VideoEncoder;
 import android.os.Environment;
@@ -42,22 +43,6 @@
     protected static final int REPEAT_NUMBER_FOR_LONG_CLIPS = 1;
     protected static final int REPEAT_NUMBER_FOR_REPEATED_PLAYBACK = 20;
     private static final String TAG = "MediaPlayerStressTest";
-    // whether a video format is supported or not.
-    private final boolean mSupported;
-
-    /**
-     * construct a test case with check of whether the format is supported or not.
-     * @param quality
-     * @param videoCodec
-     * @param audioCodec
-     */
-    protected MediaPlayerStressTest(int quality, int videoCodec, int audioCodec) {
-        mSupported = VideoPlayerCapability.formatSupported(quality, videoCodec, audioCodec);
-    }
-
-    protected MediaPlayerStressTest() {
-        mSupported = true; // supported if nothing specified
-    }
 
     /**
      * provides full path name of video clip for the given media number
@@ -120,10 +105,6 @@
      * @throws Exception
      */
     protected void doTestVideoPlayback(int mediaNumber, int repeatCounter) throws Exception {
-        if (!mSupported) {
-            return;
-        }
-
         File playbackOutput = new File(WorkDir.getTopDir(), "PlaybackTestResult.txt");
         Writer output = new BufferedWriter(new FileWriter(playbackOutput, true));
 
@@ -139,6 +120,9 @@
         Activity act = inst.startActivitySync(intent);
 
         String mediaName = getFullVideoClipName(mediaNumber);
+        if (!MediaUtils.checkCodecsForPath(inst.getTargetContext(), mediaName)) {
+            return;  // not supported, message is already logged
+        }
         for (int i = 0; i < repeatCounter; i++) {
             Log.v(TAG, "start playing " + mediaName);
             onCompleteSuccess =
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/NativeMediaTest.java b/tests/tests/mediastress/src/android/mediastress/cts/NativeMediaTest.java
index 2119d75..05145f5 100644
--- a/tests/tests/mediastress/src/android/mediastress/cts/NativeMediaTest.java
+++ b/tests/tests/mediastress/src/android/mediastress/cts/NativeMediaTest.java
@@ -17,11 +17,11 @@
 
 import android.app.Instrumentation;
 import android.content.Intent;
+import android.cts.util.MediaUtils;
 import android.media.CamcorderProfile;
+import android.media.MediaFormat;
 import android.media.MediaRecorder.AudioEncoder;
 import android.media.MediaRecorder.VideoEncoder;
-import android.media.MediaCodecInfo;
-import android.media.MediaCodecList;
 import android.os.Environment;
 import android.test.ActivityInstrumentationTestCase2;
 import android.util.Log;
@@ -30,31 +30,11 @@
 
 public class NativeMediaTest extends ActivityInstrumentationTestCase2<NativeMediaActivity> {
     private static final String TAG = "NativeMediaTest";
-    private static final String MIME_TYPE = "video/h264";
+    private static final String MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_AVC;
     private static final int VIDEO_CODEC = VideoEncoder.H264;
     private static final int NUMBER_PLAY_PAUSE_REPEATITIONS = 10;
     private static final long PLAY_WAIT_TIME_MS = 4000;
 
-    private static boolean hasCodec(String mimeType) {
-        int numCodecs = MediaCodecList.getCodecCount();
-
-        for (int i = 0; i < numCodecs; i++) {
-            MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
-
-            if (!codecInfo.isEncoder()) {
-                continue;
-            }
-
-            String[] types = codecInfo.getSupportedTypes();
-            for (int j = 0; j < types.length; j++) {
-                if (types[j].equalsIgnoreCase(mimeType)) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
     public NativeMediaTest() {
         super(NativeMediaActivity.class);
     }
@@ -77,9 +57,8 @@
 
     private void runPlayTest(int quality) throws InterruptedException {
         // Don't run the test if the codec isn't supported.
-        if (!hasCodec(MIME_TYPE)) {
-            Log.w(TAG, "Codec " + MIME_TYPE + " not supported.");
-            return;
+        if (!MediaUtils.checkDecoder(MIME_TYPE)) {
+            return; // skip
         }
         // Don't run the test if the quality level isn't supported.
         if (quality != 0) {
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/VideoPlayerCapability.java b/tests/tests/mediastress/src/android/mediastress/cts/VideoPlayerCapability.java
deleted file mode 100644
index f8dd2aa..0000000
--- a/tests/tests/mediastress/src/android/mediastress/cts/VideoPlayerCapability.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2012 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.mediastress.cts;
-
-import android.media.CamcorderProfile;
-import android.util.Log;
-
-import junit.framework.Assert;
-
-public class VideoPlayerCapability {
-    private static final String TAG = "VideoPlayCapability";
-
-    static boolean formatSupported(int quality, int videoCodec, int audioCodec) {
-        if (!CamcorderProfile.hasProfile(quality)) {
-            Log.i(TAG, "quality " + quality + " not supported");
-            return false;
-        }
-        CamcorderProfile profile = CamcorderProfile.get(quality);
-        Assert.assertNotNull(profile);
-        if ((profile.videoCodec == videoCodec) && (profile.audioCodec == audioCodec)) {
-            Log.i(TAG, "quality " + quality + " video codec " + videoCodec + " audio codec " +
-                    audioCodec + " supproted");
-            return true;
-        }
-        return false;
-    }
-
-}
diff --git a/tests/tests/net/src/android/net/cts/DnsTest.java b/tests/tests/net/src/android/net/cts/DnsTest.java
index 879a962..0377d04 100644
--- a/tests/tests/net/src/android/net/cts/DnsTest.java
+++ b/tests/tests/net/src/android/net/cts/DnsTest.java
@@ -16,6 +16,10 @@
 
 package android.net.cts;
 
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
 import android.os.SystemClock;
 import android.test.AndroidTestCase;
 import android.util.Log;
@@ -34,6 +38,7 @@
 
     private static final boolean DBG = false;
     private static final String TAG = "DnsTest";
+    private static final String PROXY_NETWORK_TYPE = "PROXY";
 
     /**
      * @return true on success
@@ -71,6 +76,14 @@
         // We should have at least one of the addresses to connect!
         assertTrue(foundV4 || foundV6);
 
+        // Skip the rest of the test if the active network for watch is PROXY.
+        // TODO: Check NetworkInfo type in addition to type name once ag/601257 is merged.
+        if (getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
+                && activeNetworkInfoIsProxy()) {
+            Log.i(TAG, "Skipping test because the active network type name is PROXY.");
+            return;
+        }
+
         try {
             addrs = InetAddress.getAllByName("ipv6.google.com");
         } catch (UnknownHostException e) {}
@@ -241,4 +254,15 @@
             Log.e(TAG, "bad URL in testDnsPerf: " + e.toString());
         }
     }
+
+    private boolean activeNetworkInfoIsProxy() {
+        ConnectivityManager cm = (ConnectivityManager)
+                getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
+        NetworkInfo info = cm.getActiveNetworkInfo();
+        if (PROXY_NETWORK_TYPE.equals(info.getTypeName())) {
+            return true;
+        }
+
+        return false;
+    }
 }
diff --git a/tests/tests/net/src/android/net/ipv6/cts/PingTest.java b/tests/tests/net/src/android/net/ipv6/cts/PingTest.java
index 49fc59c..582b81a 100644
--- a/tests/tests/net/src/android/net/ipv6/cts/PingTest.java
+++ b/tests/tests/net/src/android/net/ipv6/cts/PingTest.java
@@ -53,6 +53,9 @@
     /** Maximum size of the packets we're using to test. */
     private static final int MAX_SIZE = 4096;
 
+    /** Size of the ICMPv6 header. */
+    private static final int ICMP_HEADER_SIZE = 8;
+
     /** Number of packets to test. */
     private static final int NUM_PACKETS = 10;
 
@@ -65,7 +68,7 @@
      * Returns a byte array containing an ICMPv6 echo request with the specified payload length.
      */
     private byte[] pingPacket(int payloadLength) {
-        byte[] packet = new byte[payloadLength + 8];
+        byte[] packet = new byte[payloadLength + ICMP_HEADER_SIZE];
         new Random().nextBytes(packet);
         System.arraycopy(PING_HEADER, 0, packet, 0, PING_HEADER.length);
         return packet;
@@ -154,7 +157,7 @@
         assertEquals("localhost/::1", ipv6Loopback.toString());
 
         for (int i = 0; i < NUM_PACKETS; i++) {
-            byte[] packet = pingPacket((int) (Math.random() * MAX_SIZE));
+            byte[] packet = pingPacket((int) (Math.random() * (MAX_SIZE - ICMP_HEADER_SIZE)));
             FileDescriptor s = createPingSocket();
             // Use both recvfrom and read().
             sendPing(s, ipv6Loopback, packet);
diff --git a/tests/tests/opengl/src/android/opengl/cts/ProgramTest.java b/tests/tests/opengl/src/android/opengl/cts/ProgramTest.java
index 85009d2..dab2ef1 100644
--- a/tests/tests/opengl/src/android/opengl/cts/ProgramTest.java
+++ b/tests/tests/opengl/src/android/opengl/cts/ProgramTest.java
@@ -32,7 +32,9 @@
         intent.putExtra(OpenGLES20NativeActivityOne.EXTRA_VIEW_TYPE, viewType);
         intent.putExtra(OpenGLES20NativeActivityOne.EXTRA_VIEW_INDEX, viewIndex);
         setActivityIntent(intent);
-        return getActivity();
+        OpenGLES20ActivityOne activity = getActivity();
+        assertTrue(activity.waitForFrameDrawn());
+        return activity;
     }
 
     public void test_glAttachShader_program() throws Throwable {
diff --git a/tests/tests/os/src/android/os/cts/BuildVersionTest.java b/tests/tests/os/src/android/os/cts/BuildVersionTest.java
index e335901..3002ca3 100644
--- a/tests/tests/os/src/android/os/cts/BuildVersionTest.java
+++ b/tests/tests/os/src/android/os/cts/BuildVersionTest.java
@@ -29,7 +29,7 @@
 
     private static final String LOG_TAG = "BuildVersionTest";
     private static final Set<String> EXPECTED_RELEASES =
-            new HashSet<String>(Arrays.asList("5.0", "5.0.1", "5.0.2"));
+            new HashSet<String>(Arrays.asList("5.0.1", "5.0.2"));
     private static final int EXPECTED_SDK = 21;
     private static final String EXPECTED_BUILD_VARIANT = "user";
     private static final String EXPECTED_TAG = "release-keys";
diff --git a/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java b/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
index c31c484..babc93f 100755
--- a/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
@@ -151,6 +151,14 @@
     }
 
     @MediumTest
+    public void testDevDiagSane() throws Exception {
+        File f = new File("/dev/diag");
+        assertFalse(f.canRead());
+        assertFalse(f.canWrite());
+        assertFalse(f.canExecute());
+    }
+
+    @MediumTest
     public void testDevMemSane() throws Exception {
         File f = new File("/dev/mem");
         assertFalse(f.canRead());
diff --git a/tests/tests/permission/src/android/permission/cts/NoReadLogsPermissionTest.java b/tests/tests/permission/src/android/permission/cts/NoReadLogsPermissionTest.java
index 8979a07..7b3799d 100644
--- a/tests/tests/permission/src/android/permission/cts/NoReadLogsPermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/NoReadLogsPermissionTest.java
@@ -48,7 +48,7 @@
         BufferedReader reader = null;
         try {
             logcatProc = Runtime.getRuntime().exec(new String[]
-                    {"logcat", "-d", "ActivityManager:* *:S" });
+                    {"logcat", "-v", "brief", "-d", "ActivityManager:* *:S" });
 
             reader = new BufferedReader(new InputStreamReader(logcatProc.getInputStream()));
 
diff --git a/tests/tests/print/src/android/print/cts/BasePrintTest.java b/tests/tests/print/src/android/print/cts/BasePrintTest.java
index 1493bc9..c73bb64 100644
--- a/tests/tests/print/src/android/print/cts/BasePrintTest.java
+++ b/tests/tests/print/src/android/print/cts/BasePrintTest.java
@@ -25,6 +25,7 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.pdf.PdfDocument;
@@ -458,4 +459,8 @@
             }
         }
     }
+
+    protected boolean supportsPrinting() {
+        return getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_PRINTING);
+    }
 }
diff --git a/tests/tests/print/src/android/print/cts/PageRangeAdjustmentTest.java b/tests/tests/print/src/android/print/cts/PageRangeAdjustmentTest.java
index 4952cbd..b9fd50a 100644
--- a/tests/tests/print/src/android/print/cts/PageRangeAdjustmentTest.java
+++ b/tests/tests/print/src/android/print/cts/PageRangeAdjustmentTest.java
@@ -62,6 +62,10 @@
     private static final String FIRST_PRINTER = "First printer";
 
     public void testAllPagesWantedAndAllPagesWritten() throws Exception {
+        if (!supportsPrinting()) {
+            return;
+        }
+
         // Create a callback for the target print service.
         PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
             new Answer<PrinterDiscoverySessionCallbacks>() {
@@ -161,6 +165,10 @@
     }
 
     public void testSomePagesWantedAndAllPagesWritten() throws Exception {
+        if (!supportsPrinting()) {
+            return;
+        }
+
         // Create a callback for the target print service.
         PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
             new Answer<PrinterDiscoverySessionCallbacks>() {
@@ -269,6 +277,10 @@
     }
 
     public void testSomePagesWantedAndSomeMorePagesWritten() throws Exception {
+        if (!supportsPrinting()) {
+            return;
+        }
+
         // Create a callback for the target print service.
         PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
             new Answer<PrinterDiscoverySessionCallbacks>() {
@@ -393,6 +405,10 @@
     }
 
     public void testSomePagesWantedAndNotWritten() throws Exception {
+        if (!supportsPrinting()) {
+            return;
+        }
+
         // Create a callback for the target print service.
         PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
             new Answer<PrinterDiscoverySessionCallbacks>() {
@@ -481,6 +497,10 @@
     }
 
     public void testWantedPagesAlreadyWrittenForPreview() throws Exception {
+        if (!supportsPrinting()) {
+            return;
+        }
+
         // Create a callback for the target print service.
         PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
             new Answer<PrinterDiscoverySessionCallbacks>() {
diff --git a/tests/tests/print/src/android/print/cts/PrintDocumentAdapterContractTest.java b/tests/tests/print/src/android/print/cts/PrintDocumentAdapterContractTest.java
index 516db56..538472e 100644
--- a/tests/tests/print/src/android/print/cts/PrintDocumentAdapterContractTest.java
+++ b/tests/tests/print/src/android/print/cts/PrintDocumentAdapterContractTest.java
@@ -59,6 +59,9 @@
 public class PrintDocumentAdapterContractTest extends BasePrintTest {
 
     public void testNoPrintOptionsOrPrinterChange() throws Exception {
+        if (!supportsPrinting()) {
+            return;
+        }
         // Configure the print services.
         FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
         SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
@@ -175,6 +178,9 @@
     }
 
     public void testNoPrintOptionsOrPrinterChangeCanceled() throws Exception {
+        if (!supportsPrinting()) {
+            return;
+        }
         // Configure the print services.
         FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
         SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
@@ -265,6 +271,9 @@
     }
 
     public void testPrintOptionsChangeAndNoPrinterChange() throws Exception {
+        if (!supportsPrinting()) {
+            return;
+        }
         // Configure the print services.
         FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
         SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
@@ -439,6 +448,9 @@
     }
 
     public void testPrintOptionsChangeAndPrinterChange() throws Exception {
+        if (!supportsPrinting()) {
+            return;
+        }
         // Configure the print services.
         FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
         SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
@@ -594,6 +606,9 @@
 
     public void testPrintOptionsChangeAndNoPrinterChangeAndContentChange()
             throws Exception {
+        if (!supportsPrinting()) {
+            return;
+        }
         // Configure the print services.
         FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
         SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
@@ -718,6 +733,9 @@
     }
 
     public void testNewPrinterSupportsSelectedPrintOptions() throws Exception {
+        if (!supportsPrinting()) {
+            return;
+        }
         // Configure the print services.
         FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
         SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
@@ -820,6 +838,9 @@
     }
 
     public void testNothingChangesAllPagesWrittenFirstTime() throws Exception {
+        if (!supportsPrinting()) {
+            return;
+        }
         // Configure the print services.
         FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
         SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
@@ -941,6 +962,9 @@
     }
 
     public void testCancelLongRunningLayout() throws Exception {
+        if (!supportsPrinting()) {
+            return;
+        }
         // Configure the print services.
         FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
         SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
@@ -1015,6 +1039,9 @@
     }
 
     public void testCancelLongRunningWrite() throws Exception {
+        if (!supportsPrinting()) {
+            return;
+        }
         // Configure the print services.
         FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
         SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
@@ -1111,6 +1138,9 @@
     }
 
     public void testFailedLayout() throws Exception {
+        if (!supportsPrinting()) {
+            return;
+        }
         // Configure the print services.
         FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
         SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
@@ -1177,6 +1207,9 @@
     }
 
     public void testFailedWrite() throws Exception {
+        if (!supportsPrinting()) {
+            return;
+        }
         // Configure the print services.
         FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
         SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
@@ -1259,6 +1292,9 @@
     }
 
     public void testRequestedPagesNotWritten() throws Exception {
+        if (!supportsPrinting()) {
+            return;
+        }
         // Configure the print services.
         FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
         SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
@@ -1348,6 +1384,9 @@
     }
 
     public void testLayoutCallbackNotCalled() throws Exception {
+        if (!supportsPrinting()) {
+            return;
+        }
         // Configure the print services.
         FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
         SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
@@ -1411,6 +1450,9 @@
     }
 
     public void testWriteCallbackNotCalled() throws Exception {
+        if (!supportsPrinting()) {
+            return;
+        }
         // Configure the print services.
         FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
         SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
diff --git a/tests/tests/print/src/android/print/cts/PrinterDiscoverySessionLifecycleTest.java b/tests/tests/print/src/android/print/cts/PrinterDiscoverySessionLifecycleTest.java
index b092044..7ea09e5 100644
--- a/tests/tests/print/src/android/print/cts/PrinterDiscoverySessionLifecycleTest.java
+++ b/tests/tests/print/src/android/print/cts/PrinterDiscoverySessionLifecycleTest.java
@@ -61,6 +61,9 @@
     private static final String SECOND_PRINTER_LOCAL_ID = "second_printer";
 
     public void testNormalLifecycle() throws Exception {
+        if (!supportsPrinting()) {
+            return;
+        }
         // Create the session callbacks that we will be checking.
         final PrinterDiscoverySessionCallbacks firstSessionCallbacks =
                 createFirstMockPrinterDiscoverySessionCallbacks();
@@ -155,6 +158,9 @@
     }
 
     public void testStartPrinterDiscoveryWithHistoricalPrinters() throws Exception {
+        if (!supportsPrinting()) {
+            return;
+        }
         // Create the session callbacks that we will be checking.
         final PrinterDiscoverySessionCallbacks firstSessionCallbacks =
                 createFirstMockPrinterDiscoverySessionCallbacks();
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Video_ThumbnailsTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Video_ThumbnailsTest.java
index b6175be..974eeb7 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Video_ThumbnailsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_Video_ThumbnailsTest.java
@@ -22,6 +22,7 @@
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.cts.util.FileCopyHelper;
+import android.cts.util.MediaUtils;
 import android.database.Cursor;
 import android.media.MediaCodecInfo;
 import android.media.MediaCodecList;
@@ -38,32 +39,13 @@
 
 public class MediaStore_Video_ThumbnailsTest extends AndroidTestCase {
     private static final String TAG = "MediaStore_Video_ThumbnailsTest";
-    private static final String MIME_TYPE = "video/3gpp";
 
     private ContentResolver mResolver;
 
     private FileCopyHelper mFileHelper;
 
-    // TODO: Make a public method selectCodec() in common libraries (e.g. cts/libs/), to avoid
-    // redundant function definitions in this and other media related test files.
-    private static boolean hasCodec(String mimeType) {
-        int numCodecs = MediaCodecList.getCodecCount();
-
-        for (int i = 0; i < numCodecs; i++) {
-            MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
-
-            if (!codecInfo.isEncoder()) {
-                continue;
-            }
-
-            String[] types = codecInfo.getSupportedTypes();
-            for (int j = 0; j < types.length; j++) {
-                if (types[j].equalsIgnoreCase(mimeType)) {
-                    return true;
-                }
-            }
-        }
-        return false;
+    private boolean hasCodec() {
+        return MediaUtils.hasCodecForResourceAndDomain(mContext, R.raw.testvideo, "video/");
     }
 
     @Override
@@ -98,10 +80,10 @@
         int count = getThumbnailCount(Thumbnails.EXTERNAL_CONTENT_URI);
 
         // Don't run the test if the codec isn't supported.
-        if (!hasCodec(MIME_TYPE)) {
+        if (!hasCodec()) {
             // Calling getThumbnail should not generate a new thumbnail.
             assertNull(Thumbnails.getThumbnail(mResolver, videoId, Thumbnails.MINI_KIND, null));
-            Log.w(TAG, "Codec " + MIME_TYPE + " not supported. Return from testGetThumbnail.");
+            Log.i(TAG, "SKIPPING testGetThumbnail(): codec not supported");
             return;
         }
 
diff --git a/tests/tests/security/Android.mk b/tests/tests/security/Android.mk
index de58783..c41ee58 100644
--- a/tests/tests/security/Android.mk
+++ b/tests/tests/security/Android.mk
@@ -34,22 +34,6 @@
 
 LOCAL_SDK_VERSION := current
 
-intermediates.COMMON := $(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),,COMMON)
-
-sepolicy_asset_dir := $(intermediates.COMMON)/assets
-
-LOCAL_ASSET_DIR := $(sepolicy_asset_dir)
-
 include $(BUILD_CTS_PACKAGE)
 
-selinux_policy.xml := $(sepolicy_asset_dir)/selinux_policy.xml
-selinux_policy_parser := cts/tools/selinux/src/gen_SELinux_CTS.py
-general_sepolicy_policy.conf := $(call intermediates-dir-for,ETC,general_sepolicy.conf)/general_sepolicy.conf
-$(selinux_policy.xml): PRIVATE_POLICY_PARSER := $(selinux_policy_parser)
-$(selinux_policy.xml): $(general_sepolicy_policy.conf) $(selinux_policy_parser)
-	mkdir -p $(dir $@)
-	$(PRIVATE_POLICY_PARSER) $< $@ neverallow_only=t
-
-$(R_file_stamp): $(selinux_policy.xml)
-
 include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/security/src/android/security/cts/BannedFilesTest.java b/tests/tests/security/src/android/security/cts/BannedFilesTest.java
index 8076f8e..00c4631 100644
--- a/tests/tests/security/src/android/security/cts/BannedFilesTest.java
+++ b/tests/tests/security/src/android/security/cts/BannedFilesTest.java
@@ -131,14 +131,6 @@
         assertNotSetugid("/vendor/bin/tcpdump-arm");
     }
 
-    /**
-     * Test if /dev/diag exists.
-     */
-    public void testNoDevDiag(){
-        File file = new File("/dev/diag");
-        assertFalse("File \"" + file.getAbsolutePath() + "\" exists", file.exists());
-    }
-
     private static void assertNotSetugid(String file) {
         FileUtils.FileStatus fs = new FileUtils.FileStatus();
         if (!FileUtils.getFileStatus(file, fs, false)) {
diff --git a/tests/tests/security/src/android/security/cts/SELinuxDomainTest.java b/tests/tests/security/src/android/security/cts/SELinuxDomainTest.java
index ee1b027..66054f9 100644
--- a/tests/tests/security/src/android/security/cts/SELinuxDomainTest.java
+++ b/tests/tests/security/src/android/security/cts/SELinuxDomainTest.java
@@ -199,7 +199,7 @@
 
     /* drm server is always present */
     public void testDrmServerDomain() throws FileNotFoundException {
-        assertDomainOne("u:r:drmserver:s0", "/system/bin/drmserver");
+        assertDomainZeroOrOne("u:r:drmserver:s0", "/system/bin/drmserver");
     }
 
     /* Media server is always running */
diff --git a/tests/tests/security/src/android/security/cts/SELinuxPolicyRule.java b/tests/tests/security/src/android/security/cts/SELinuxPolicyRule.java
deleted file mode 100644
index d06fd75..0000000
--- a/tests/tests/security/src/android/security/cts/SELinuxPolicyRule.java
+++ /dev/null
@@ -1,162 +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.security.cts;
-
-import android.util.Xml;
-
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.Multimap;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.InputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.HashMap;
-
-
-/**
- * A class for generating representations of SELinux avc rules parsed from an xml file.
- */
-public class SELinuxPolicyRule {
-    public final List<String> source_types;
-    public final List<String> target_types;
-    public final Multimap<String, String> obj_classes;
-    public final String name;
-    public final String type;
-
-    private SELinuxPolicyRule(List<String> source_types, List<String> target_types,
-            Multimap<String, String> obj_classes, String name, String type) {
-        this.source_types = source_types;
-        this.target_types = target_types;
-        this.obj_classes = obj_classes;
-        this.name = name;
-        this.type = type;
-    }
-
-    public static SELinuxPolicyRule readRule(XmlPullParser xpp) throws IOException, XmlPullParserException {
-        List<String> source_types = new ArrayList<String>();
-        List<String> target_types = new ArrayList<String>();
-        Multimap<String, String> obj_classes = HashMultimap.create();
-        xpp.require(XmlPullParser.START_TAG, null, "avc_rule");
-        String ruleName = xpp.getAttributeValue(null, "name");
-        String ruleType = xpp.getAttributeValue(null, "type");
-        while (xpp.next() != XmlPullParser.END_TAG) {
-            if (xpp.getEventType() != XmlPullParser.START_TAG) {
-                continue;
-            }
-            String name = xpp.getName();
-            if (name.equals("type")) {
-                if (xpp.getAttributeValue(null, "type").equals("source")) {
-                    source_types.add(readType(xpp));
-                } else if (xpp.getAttributeValue(null, "type").equals("target")) {
-                    target_types.add(readType(xpp));
-                } else {
-                    skip(xpp);
-                }
-            } else if (name.equals("obj_class")) {
-                String obj_name = xpp.getAttributeValue(null, "name");
-                List<String> perms = readObjClass(xpp);
-                obj_classes.putAll(obj_name, perms);
-            } else {
-                skip(xpp);
-            }
-        }
-        return new SELinuxPolicyRule(source_types, target_types, obj_classes, ruleName, ruleType);
-    }
-
-    public static List<SELinuxPolicyRule> readRulesFile(InputStream in) throws IOException, XmlPullParserException {
-        List<SELinuxPolicyRule> rules = new ArrayList<SELinuxPolicyRule>();
-        XmlPullParser xpp = Xml.newPullParser();
-        xpp.setInput(in, null);
-        xpp.nextTag();
-        xpp.require(XmlPullParser.START_TAG, null, "SELinux_AVC_Rules");
-
-        /* read rules */
-        while (xpp.next()  != XmlPullParser.END_TAG) {
-            if (xpp.getEventType() != XmlPullParser.START_TAG) {
-                continue;
-            }
-            String name = xpp.getName();
-            if (name.equals("avc_rule")) {
-                SELinuxPolicyRule r = readRule(xpp);
-                rules.add(r);
-            } else {
-                skip(xpp);
-            }
-        }
-        return rules;
-    }
-
-    private static List<String> readObjClass(XmlPullParser xpp) throws IOException, XmlPullParserException {
-        List<String> perms = new ArrayList<String>();
-        xpp.require(XmlPullParser.START_TAG, null, "obj_class");
-        while (xpp.next() != XmlPullParser.END_TAG) {
-        if (xpp.getEventType() != XmlPullParser.START_TAG) {
-                continue;
-            }
-            String name = xpp.getName();
-            if (name.equals("permission")) {
-                perms.add(readPermission(xpp));
-            } else {
-                skip(xpp);
-            }
-        }
-        return perms;
-    }
-
-    private static String readType(XmlPullParser xpp) throws IOException, XmlPullParserException {
-        xpp.require(XmlPullParser.START_TAG, null, "type");
-        String type = readText(xpp);
-        xpp.require(XmlPullParser.END_TAG, null, "type");
-        return type;
-    }
-
-    private static String readPermission(XmlPullParser xpp) throws IOException, XmlPullParserException {
-        xpp.require(XmlPullParser.START_TAG, null, "permission");
-        String permission = readText(xpp);
-        xpp.require(XmlPullParser.END_TAG, null, "permission");
-        return permission;
-    }
-
-    private static String readText(XmlPullParser xpp) throws IOException, XmlPullParserException {
-        String result = "";
-        if (xpp.next() == XmlPullParser.TEXT) {
-            result = xpp.getText();
-            xpp.nextTag();
-        }
-        return result;
-    }
-
-    public static void skip(XmlPullParser xpp) throws XmlPullParserException, IOException {
-        if (xpp.getEventType() != XmlPullParser.START_TAG) {
-            throw new IllegalStateException();
-        }
-        int depth = 1;
-        while (depth != 0) {
-            switch (xpp.next()) {
-            case XmlPullParser.END_TAG:
-                depth--;
-                break;
-            case XmlPullParser.START_TAG:
-                depth++;
-                break;
-            }
-        }
-    }
-}
diff --git a/tests/tests/security/src/android/security/cts/SELinuxTest.java b/tests/tests/security/src/android/security/cts/SELinuxTest.java
index 8e57037..711cb91 100644
--- a/tests/tests/security/src/android/security/cts/SELinuxTest.java
+++ b/tests/tests/security/src/android/security/cts/SELinuxTest.java
@@ -18,7 +18,6 @@
 
 import android.content.Context;
 import android.content.res.AssetManager;
-import android.security.cts.SELinuxPolicyRule;
 import android.test.AndroidTestCase;
 
 import junit.framework.TestCase;
@@ -82,130 +81,6 @@
         assertEquals(0, files.length);
     }
 
-    /**
-     * Verify all of the rules described by the selinux_policy.xml file are in effect.  Allow rules
-     * should return access granted, and Neverallow should return access denied.  All checks are run
-     * and then a list of specific failed checks is printed.
-     */
-    public void testSELinuxPolicyFile() throws IOException, XmlPullParserException {
-        List<String> failedChecks = new ArrayList<String>();
-        Map<String, Boolean> contextsCache = new HashMap<String, Boolean>();
-        int invalidContextsCount = 0;
-        int totalChecks = 0;
-        int totalFailedChecks = 0;
-        AssetManager assets = mContext.getAssets();
-        InputStream in = assets.open("selinux_policy.xml");
-        Collection<SELinuxPolicyRule> rules = SELinuxPolicyRule.readRulesFile(in);
-        for (SELinuxPolicyRule r : rules) {
-            PolicyFileTestResult result = runRuleChecks(r, contextsCache);
-            totalChecks += result.numTotalChecks;
-            if (result.numFailedChecks != 0) {
-                totalFailedChecks += result.numFailedChecks;
-
-                /* print failures to log, so as not to run OOM in the event of large policy mismatch,
-                   but record actual rule type and number */
-                failedChecks.add("SELinux avc rule " + r.type + r.name + " failed " + result.numFailedChecks +
-                        " out of " + result.numTotalChecks + " checks.");
-                for (String k : result.failedChecks) {
-                    System.out.println(r.type + r.name + " failed " + k);
-                }
-            }
-        }
-        if (totalFailedChecks != 0) {
-
-            /* print out failed rules, just the rule number and type */
-            for (String k : failedChecks) {
-                System.out.println(k);
-            }
-            System.out.println("Failed SELinux Policy Test: " + totalFailedChecks + " failed out of " + totalChecks);
-        }
-        for (String k : contextsCache.keySet()) {
-            if (!contextsCache.get(k)) {
-                invalidContextsCount++;
-                System.out.println("Invalid SELinux context encountered: " + k);
-            }
-        }
-        System.out.println("SELinuxPolicy Test Encountered: " + invalidContextsCount + " missing contexts out of " + contextsCache.size());
-        assertTrue(totalFailedChecks == 0);
-    }
-
-    /**
-     * A class for containing all of the results we care to know from checking each SELinux rule
-     */
-    private class PolicyFileTestResult {
-        private int numTotalChecks;
-        private int numFailedChecks;
-        private List<String> failedChecks = new ArrayList<String>();
-    }
-
-    private PolicyFileTestResult runRuleChecks(SELinuxPolicyRule r, Map<String, Boolean> contextsCache) {
-        PolicyFileTestResult result = new PolicyFileTestResult();
-
-        /* run checks by going through every possible 4-tuple specified by rule.  Start with class
-           and perm to allow early-exit based on context. */
-        for (String c : r.obj_classes.keySet()) {
-            for (String p : r.obj_classes.get(c)) {
-                for (String s : r.source_types) {
-
-                    /* check source context */
-                    String source_context = createAvcContext(s, false, c, p);
-                    if (!contextsCache.containsKey(source_context)) {
-                        contextsCache.put(source_context, checkSELinuxContext(source_context));
-                    }
-                    if (!contextsCache.get(source_context)) {
-                        continue;
-                    }
-                    for (String t : r.target_types) {
-                        if (t.equals("self")) {
-                            t = s;
-                        }
-
-                        /* check target context */
-                        String target_context = createAvcContext(t, true, c, p);
-                        if (!contextsCache.containsKey(target_context)) {
-                            contextsCache.put(target_context, checkSELinuxContext(target_context));
-                        }
-                        if (!contextsCache.get(target_context)) {
-                            continue;
-                        }
-                        boolean canAccess  = checkSELinuxAccess(source_context, target_context,
-                                c, p, "");
-                        result.numTotalChecks++;
-                        if ((r.type.equals("allow") && !canAccess)
-                                || (r.type.equals("neverallow") && canAccess)) {
-                            String failureNotice = s + ", " + t + ", " + c + ", " + p;
-                            result.numFailedChecks++;
-                            result.failedChecks.add(failureNotice);
-                        }
-                    }
-                }
-            }
-        }
-        return result;
-    }
-
-    /* createAvcContext - currently uses class type and perm to determine user, role and mls values.
-     *
-     * @param target - false if source domain, true if target.
-     */
-    private String createAvcContext(String domain, boolean target,
-            String obj_class, String perm) {
-        String usr = "u";
-        String role;
-
-        /* understand role labeling better */
-        if (obj_class.equals("filesystem") && perm.equals("associate")) {
-            role = "object_r";
-        } else if(obj_class.equals("process") || obj_class.endsWith("socket")) {
-            role = "r";
-        } else if (target) {
-            role = "object_r";
-        } else {
-            role = "r";
-        }
-        return String.format("%s:%s:%s:s0", usr, role, domain);
-    }
-
     private static native boolean checkSELinuxAccess(String scon, String tcon, String tclass, String perm, String extra);
 
     private static native boolean checkSELinuxContext(String con);
diff --git a/tests/tests/speech/src/android/speech/tts/cts/TextToSpeechTest.java b/tests/tests/speech/src/android/speech/tts/cts/TextToSpeechTest.java
index 799fd8d..69acdd0 100644
--- a/tests/tests/speech/src/android/speech/tts/cts/TextToSpeechTest.java
+++ b/tests/tests/speech/src/android/speech/tts/cts/TextToSpeechTest.java
@@ -15,6 +15,7 @@
  */
 package android.speech.tts.cts;
 
+import android.content.pm.PackageManager;
 import android.os.Environment;
 import android.speech.tts.TextToSpeech;
 import android.test.AndroidTestCase;
@@ -39,6 +40,15 @@
     protected void setUp() throws Exception {
         super.setUp();
         mTts = TextToSpeechWrapper.createTextToSpeechWrapper(getContext());
+        if (mTts == null) {
+            PackageManager pm = getContext().getPackageManager();
+            if (!pm.hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
+                // It is OK to have no TTS, when audio-out is not supported.
+                return;
+            } else {
+                fail("FEATURE_AUDIO_OUTPUT is set, but there is no TTS engine");
+            }
+        }
         assertNotNull(mTts);
         assertTrue(checkAndSetLanguageAvailable());
     }
@@ -46,7 +56,9 @@
     @Override
     protected void tearDown() throws Exception {
         super.tearDown();
-        mTts.shutdown();
+        if (mTts != null) {
+            mTts.shutdown();
+        }
     }
 
     private TextToSpeech getTts() {
@@ -83,6 +95,9 @@
     }
 
     public void testSynthesizeToFile() throws Exception {
+        if (mTts == null) {
+            return;
+        }
         File sampleFile = new File(Environment.getExternalStorageDirectory(), SAMPLE_FILE_NAME);
         try {
             assertFalse(sampleFile.exists());
@@ -101,18 +116,27 @@
     }
 
     public void testSpeak() throws Exception {
+        if (mTts == null) {
+            return;
+        }
         int result = getTts().speak(SAMPLE_TEXT, TextToSpeech.QUEUE_FLUSH, createParams());
         assertEquals("speak() failed", TextToSpeech.SUCCESS, result);
         assertTrue("speak() completion timeout", waitForUtterance());
     }
 
     public void testGetEnginesIncludesDefault() throws Exception {
+        if (mTts == null) {
+            return;
+        }
         List<TextToSpeech.EngineInfo> engines = getTts().getEngines();
         assertNotNull("getEngines() returned null", engines);
         assertContainsEngine(getTts().getDefaultEngine(), engines);
     }
 
     public void testGetEnginesIncludesMock() throws Exception {
+        if (mTts == null) {
+            return;
+        }
         List<TextToSpeech.EngineInfo> engines = getTts().getEngines();
         assertNotNull("getEngines() returned null", engines);
         assertContainsEngine(TextToSpeechWrapper.MOCK_TTS_ENGINE, engines);
diff --git a/tests/tests/telephony/AndroidManifest.xml b/tests/tests/telephony/AndroidManifest.xml
index b3ae1a3..31abf12 100644
--- a/tests/tests/telephony/AndroidManifest.xml
+++ b/tests/tests/telephony/AndroidManifest.xml
@@ -28,6 +28,7 @@
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
     <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+    <uses-permission android:name="android.permission.BLUETOOTH" />
 
     <application>
         <uses-library android:name="android.test.runner" />
diff --git a/tests/tests/text/src/android/text/format/cts/TimeTest.java b/tests/tests/text/src/android/text/format/cts/TimeTest.java
index 2d623e3..cc73272 100644
--- a/tests/tests/text/src/android/text/format/cts/TimeTest.java
+++ b/tests/tests/text/src/android/text/format/cts/TimeTest.java
@@ -36,16 +36,20 @@
 
     private Locale originalLocale;
 
+    private static List<Locale> sSystemLocales;
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();
         originalLocale = Locale.getDefault();
+
+        maybeInitializeSystemLocales();
     }
 
     @Override
     public void tearDown() throws Exception {
         // The Locale may be changed by tests. Revert to the original.
-        changeJavaAndAndroidLocale(originalLocale);
+        changeJavaAndAndroidLocale(originalLocale, true /* force */);
         super.tearDown();
     }
 
@@ -771,7 +775,10 @@
     }
 
     public void testFormat_tokensUkLocale() throws Exception {
-        changeJavaAndAndroidLocale(Locale.UK);
+        if (!changeJavaAndAndroidLocale(Locale.UK, false /* force */)) {
+            Log.w(TAG, "Skipping testFormat_tokensUkLocale: no assets found");
+            return;
+        }
 
         Time t = new Time("Europe/London");
         Fields.setDateTime(t, 2005, 5, 1, 12, 30, 15);
@@ -863,7 +870,10 @@
     }
 
     public void testFormat_tokensUsLocale() throws Exception {
-        changeJavaAndAndroidLocale(Locale.US);
+        if (!changeJavaAndAndroidLocale(Locale.US, false /* force */)) {
+            Log.w(TAG, "Skipping testFormat_tokensUSLocale: no assets found");
+            return;
+        }
 
         Time t = new Time("America/New_York");
         Fields.setDateTime(t, 2005, 5, 1, 12, 30, 15);
@@ -955,7 +965,10 @@
     }
 
     public void testFormat_tokensFranceLocale() throws Exception {
-        changeJavaAndAndroidLocale(Locale.FRANCE);
+        if (!changeJavaAndAndroidLocale(Locale.FRANCE, false /* force */)) {
+            Log.w(TAG, "Skipping testFormat_tokensFranceLocale: no assets found");
+            return;
+        }
 
         Time t = new Time("Europe/Paris");
         Fields.setDateTime(t, 2005, 5, 1, 12, 30, 15);
@@ -1047,7 +1060,10 @@
     }
 
     public void testFormat_tokensJapanLocale() throws Exception {
-        changeJavaAndAndroidLocale(Locale.JAPAN);
+        if (!changeJavaAndAndroidLocale(Locale.JAPAN, false /* force */)) {
+            Log.w(TAG, "Skipping testFormat_tokensJapanLocale: no assets found");
+            return;
+        }
 
         Time t = new Time("Asia/Tokyo");
         Fields.setDateTime(t, 2005, 5, 1, 12, 30, 15);
@@ -2843,7 +2859,19 @@
         }
     }
 
-    private static void changeJavaAndAndroidLocale(Locale locale) {
+    private static void maybeInitializeSystemLocales() {
+        if (sSystemLocales == null) {
+            String[] locales = Resources.getSystem().getAssets().getLocales();
+            List<Locale> systemLocales = new ArrayList<Locale>(locales.length);
+            for (String localeStr : locales) {
+                systemLocales.add(Locale.forLanguageTag(localeStr));
+            }
+
+            sSystemLocales = systemLocales;
+        }
+    }
+
+    private static boolean changeJavaAndAndroidLocale(Locale locale, boolean force) {
         // The Time class uses the Android-managed locale for string resources containing format
         // patterns and the Java-managed locale for other things (e.g. month names, week days names)
         // that are placed into those patterns. For example the format "%c" expands to include
@@ -2856,6 +2884,10 @@
         // locales agree), when the Java-managed locale is changed during this test the locale in
         // the runtime-local copy of the system resources is modified as well.
 
+        if (!force && !sSystemLocales.contains(locale)) {
+            return false;
+        }
+
         // Change the Java-managed locale.
         Locale.setDefault(locale);
 
@@ -2864,5 +2896,6 @@
         Configuration configuration = Resources.getSystem().getConfiguration();
         configuration.locale = locale;
         Resources.getSystem().updateConfiguration(configuration, null);
+        return true;
     }
 }
diff --git a/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTest.java b/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTest.java
index 6d80819..d267d63 100644
--- a/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTest.java
+++ b/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTest.java
@@ -272,7 +272,7 @@
             if (lastPresentedTimeNano == FrameStats.UNDEFINED_TIME_NANO) {
                 assertTrue(presentedTimeNano == FrameStats.UNDEFINED_TIME_NANO);
             } else if (presentedTimeNano != FrameStats.UNDEFINED_TIME_NANO) {
-                assertTrue(presentedTimeNano > lastPresentedTimeNano);
+                assertTrue(presentedTimeNano >= lastPresentedTimeNano);
             }
             lastPresentedTimeNano = presentedTimeNano;
         }
diff --git a/tests/tests/uirendering/res/layout/blue_padded_layout.xml b/tests/tests/uirendering/res/layout/blue_padded_layout.xml
index 1cd1b21..68c9cd1 100644
--- a/tests/tests/uirendering/res/layout/blue_padded_layout.xml
+++ b/tests/tests/uirendering/res/layout/blue_padded_layout.xml
@@ -14,6 +14,7 @@
        limitations under the License.
   -->
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/test_root"
         android:layout_width="200px"
         android:layout_height="200px"
         android:clipChildren="false">
diff --git a/tests/tests/uirendering/res/layout/blue_padded_square.xml b/tests/tests/uirendering/res/layout/blue_padded_square.xml
index 0f254d4..71f4b0c 100644
--- a/tests/tests/uirendering/res/layout/blue_padded_square.xml
+++ b/tests/tests/uirendering/res/layout/blue_padded_square.xml
@@ -14,6 +14,7 @@
        limitations under the License.
   -->
 <View xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="100px"
-        android:layout_height="100px"
-        android:background="@drawable/blue_padded_square"/>
+    android:id="@+id/test_root"
+    android:layout_width="100px"
+    android:layout_height="100px"
+    android:background="@drawable/blue_padded_square"/>
diff --git a/tests/tests/uirendering/res/layout/simple_rect_layout.xml b/tests/tests/uirendering/res/layout/simple_rect_layout.xml
index e64c4e9..b570df8 100644
--- a/tests/tests/uirendering/res/layout/simple_rect_layout.xml
+++ b/tests/tests/uirendering/res/layout/simple_rect_layout.xml
@@ -13,11 +13,11 @@
        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:id="@+id/test_root"
     android:layout_width="match_parent"
-    android:layout_height="match_parent">
+    android:layout_height="match_parent"
+    android:orientation="vertical">
 
     <View android:layout_width="100px"
         android:layout_height="100px"
diff --git a/tests/tests/uirendering/res/layout/simple_red_layout.xml b/tests/tests/uirendering/res/layout/simple_red_layout.xml
index 1ae3e38..2d2d189 100644
--- a/tests/tests/uirendering/res/layout/simple_red_layout.xml
+++ b/tests/tests/uirendering/res/layout/simple_red_layout.xml
@@ -14,6 +14,7 @@
        limitations under the License.
   -->
 <View xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
+    android:id="@+id/test_root"
+    android:layout_width="180px"
+    android:layout_height="180px"
     android:background="#f00" />
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/bitmapcomparers/ExactComparer.java b/tests/tests/uirendering/src/android/uirendering/cts/bitmapcomparers/ExactComparer.java
index 978dc0b..36be5f0 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/bitmapcomparers/ExactComparer.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/bitmapcomparers/ExactComparer.java
@@ -41,9 +41,11 @@
             for (int x = 0 ; x < width ; x++) {
                 int index = indexFromXAndY(x, y, stride, offset);
                 if (ideal[index] != given[index]) {
-                    Log.d(TAG, "Failure on position x = " + x + " y = " + y);
-                    Log.d(TAG, "Expected color : " + Integer.toHexString(ideal[index]) +
-                            " given color : " + Integer.toHexString(given[index]));
+                    if (count < 50) {
+                        Log.d(TAG, "Failure on position x = " + x + " y = " + y);
+                        Log.d(TAG, "Expected color : " + Integer.toHexString(ideal[index]) +
+                                " given color : " + Integer.toHexString(given[index]));
+                    }
                     count++;
                 }
             }
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/PerPixelBitmapVerifier.java b/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/PerPixelBitmapVerifier.java
index ab809f4..0bdcc9b 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/PerPixelBitmapVerifier.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/PerPixelBitmapVerifier.java
@@ -38,30 +38,33 @@
 
 
     public boolean verify(int[] bitmap, int offset, int stride, int width, int height) {
-        boolean res = true;
+        int failCount = 0;
         int[] differenceMap = new int[bitmap.length];
         for (int y = 0 ; y < height ; y++) {
             for (int x = 0 ; x < width ; x++) {
                 int index = indexFromXAndY(x, y, stride, offset);
                 int expectedColor = getExpectedColor(x, y);
                 if (!verifyPixel(bitmap[index], expectedColor)) {
-                    Log.d(TAG, "Expected : " + Integer.toHexString(expectedColor)
-                            + " received : " + Integer.toHexString(bitmap[index])
-                            + " at position (" + x + "," + y + ")");
-                    res = false;
+                    if (failCount < 50) {
+                        Log.d(TAG, "Expected : " + Integer.toHexString(expectedColor)
+                                + " received : " + Integer.toHexString(bitmap[index])
+                                + " at position (" + x + "," + y + ")");
+                    }
+                    failCount++;
                     differenceMap[index] = FAIL_COLOR;
                 } else {
                     differenceMap[index] = PASS_COLOR;
                 }
             }
         }
-        if (!res) {
+        boolean success = failCount == 0;
+        if (!success) {
             mDifferenceBitmap = Bitmap.createBitmap(ActivityTestBase.TEST_WIDTH,
                     ActivityTestBase.TEST_HEIGHT, Bitmap.Config.ARGB_8888);
             mDifferenceBitmap.setPixels(differenceMap, offset, stride, 0, 0,
                     ActivityTestBase.TEST_WIDTH, ActivityTestBase.TEST_HEIGHT);
         }
-        return res;
+        return success;
     }
 
     protected boolean verifyPixel(int color, int expectedColor) {
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayoutTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayoutTests.java
index 0ba0f69..4667ee9 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayoutTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayoutTests.java
@@ -15,32 +15,26 @@
  */
 package android.uirendering.cts.testclasses;
 
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.uirendering.cts.bitmapverifiers.ColorVerifier;
+import android.uirendering.cts.bitmapverifiers.RectVerifier;
 import com.android.cts.uirendering.R;
 
 import android.test.suitebuilder.annotation.SmallTest;
-import android.uirendering.cts.bitmapcomparers.BitmapComparer;
-import android.uirendering.cts.bitmapcomparers.ExactComparer;
 import android.uirendering.cts.testinfrastructure.ActivityTestBase;
 
-
-/**
- * Created to see how custom views made with XML and programatic code will work.
- */
 public class LayoutTests extends ActivityTestBase {
-    private BitmapComparer mBitmapComparer;
-
-    public LayoutTests() {
-        mBitmapComparer = new ExactComparer();
-    }
-
     @SmallTest
     public void testSimpleRedLayout() {
-        createTest().addLayout(R.layout.simple_red_layout, null).runWithComparer(mBitmapComparer);
+        createTest().addLayout(R.layout.simple_red_layout, null, false).runWithVerifier(
+                new ColorVerifier(Color.RED));
     }
 
     @SmallTest
     public void testSimpleRectLayout() {
-        createTest().addLayout(R.layout.simple_rect_layout, null).runWithComparer(mBitmapComparer);
+        createTest().addLayout(R.layout.simple_rect_layout, null, false).runWithVerifier(
+                new RectVerifier(Color.WHITE, Color.BLUE, new Rect(0, 0, 100, 100)));
     }
 }
 
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ViewClippingTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ViewClippingTests.java
index 1acdc20..da2db48 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ViewClippingTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ViewClippingTests.java
@@ -62,7 +62,7 @@
     };
 
     // TODO: attempt to reduce
-    static final int TOLERANCE = 10;
+    static final int TOLERANCE = 16;
     static BitmapVerifier makeClipVerifier(Rect blueBoundsRect) {
         return new RectVerifier(Color.WHITE, Color.BLUE, blueBoundsRect, TOLERANCE);
     }
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/ActivityTestBase.java b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/ActivityTestBase.java
index 052b251..783e710 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/ActivityTestBase.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/ActivityTestBase.java
@@ -38,7 +38,7 @@
  */
 public abstract class ActivityTestBase extends
         ActivityInstrumentationTestCase2<DrawActivity> {
-    public static final String TAG_NAME = "ActivityTestBase";
+    public static final String TAG = "ActivityTestBase";
     public static final boolean DEBUG = false;
     public static final boolean USE_RS = false;
     public static final int TEST_WIDTH = 180;
@@ -97,7 +97,7 @@
 
             for (TestCase testCase : testCases) {
                 if (!testCase.wasTestRan) {
-                    Log.w(TAG_NAME, getName() + " not all of the tests were ran");
+                    Log.w(TAG, getName() + " not all of the tests ran");
                     break;
                 }
             }
@@ -213,6 +213,11 @@
          * every test case is tested against it.
          */
         public void runWithComparer(BitmapComparer bitmapComparer) {
+            if (getActivity().getOnWatch()) {
+                Log.d(TAG, getName() + "skipped");
+                return;
+            }
+
             if (mTestCases.size() == 0) {
                 throw new IllegalStateException("Need at least one test to run");
             }
@@ -231,6 +236,11 @@
          * the verifier given.
          */
         public void runWithVerifier(BitmapVerifier bitmapVerifier) {
+            if (getActivity().getOnWatch()) {
+                Log.d(TAG, getName() + "skipped");
+                return;
+            }
+
             if (mTestCases.size() == 0) {
                 throw new IllegalStateException("Need at least one test to run");
             }
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DrawActivity.java b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DrawActivity.java
index 4d4a012..166b6ff 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DrawActivity.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DrawActivity.java
@@ -17,15 +17,16 @@
 
 import android.annotation.Nullable;
 import android.app.Activity;
-import android.content.Context;
+import android.content.res.Configuration;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
-import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewTreeObserver;
 import android.webkit.WebView;
 
+import com.android.cts.uirendering.R;
+
 /**
  * A generic activity that uses a view specified by the user.
  */
@@ -36,16 +37,17 @@
 
     private Handler mHandler;
     private View mView;
+    private boolean mOnWatch;
 
     public void onCreate(Bundle bundle){
         super.onCreate(bundle);
         mHandler = new RenderSpecHandler();
+        int uiMode = getResources().getConfiguration().uiMode;
+        mOnWatch = (uiMode & Configuration.UI_MODE_TYPE_WATCH) == Configuration.UI_MODE_TYPE_WATCH;
     }
 
-    @Override
-    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
-        mView = parent;
-        return super.onCreateView(parent, name, context, attrs);
+    public boolean getOnWatch() {
+        return mOnWatch;
     }
 
     public void enqueueRenderSpecAndWait(int layoutId, CanvasClient canvasClient, String webViewUrl,
@@ -85,6 +87,10 @@
             switch (message.what) {
                 case LAYOUT_MSG: {
                     setContentView(message.arg1);
+                    mView = findViewById(R.id.test_root);
+                    if (mView == null) {
+                        throw new IllegalStateException("test_root failed to inflate");
+                    }
                 } break;
 
                 case CANVAS_MSG: {
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/util/BitmapDumper.java b/tests/tests/uirendering/src/android/uirendering/cts/util/BitmapDumper.java
index 41e255b..8dd98b0 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/util/BitmapDumper.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/util/BitmapDumper.java
@@ -99,9 +99,25 @@
         saveFile(className, testName, SINGULAR_FILE_NAME, bitmap);
     }
 
+    private static void logIfBitmapSolidColor(String bitmapName, Bitmap bitmap) {
+        int firstColor = bitmap.getPixel(0, 0);
+        for (int x = 0; x < bitmap.getWidth(); x++) {
+            for (int y = 0; y < bitmap.getHeight(); y++) {
+                if (bitmap.getPixel(x, y) != firstColor) {
+                    return;
+                }
+            }
+        }
+
+        Log.w(TAG, String.format("%s entire bitmap color is %x", bitmapName, firstColor));
+    }
+
     private static void saveFile(String className, String testName, String fileName, Bitmap bitmap) {
-        Log.d(TAG, "Saving file : " + testName + "_" + fileName + " in directory : " + className);
-        File file = new File(CAPTURE_SUB_DIRECTORY + className, testName + "_" + fileName);
+        String bitmapName = testName + "_" + fileName;
+        Log.d(TAG, "Saving file : " + bitmapName + " in directory : " + className);
+        logIfBitmapSolidColor(bitmapName, bitmap);
+
+        File file = new File(CAPTURE_SUB_DIRECTORY + className, bitmapName);
         FileOutputStream fileStream = null;
         try {
             fileStream = new FileOutputStream(file);
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
index 1e22acc..b381d72 100755
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
@@ -1557,6 +1557,37 @@
         assertEquals(1, handler.getMsgArg1());
     }
 
+    private static void waitForFlingDone(WebViewOnUiThread webview) {
+        class ScrollDiffPollingCheck extends PollingCheck {
+            private static final long TIME_SLICE = 50;
+            WebViewOnUiThread mWebView;
+            private int mScrollX;
+            private int mScrollY;
+
+            ScrollDiffPollingCheck(WebViewOnUiThread webview) {
+                mWebView = webview;
+                mScrollX = mWebView.getScrollX();
+                mScrollY = mWebView.getScrollY();
+            }
+
+            @Override
+            protected boolean check() {
+                try {
+                    Thread.sleep(TIME_SLICE);
+                } catch (InterruptedException e) {
+                    // Intentionally ignored.
+                }
+                int newScrollX = mWebView.getScrollX();
+                int newScrollY = mWebView.getScrollY();
+                boolean flingDone = newScrollX == mScrollX && newScrollY == mScrollY;
+                mScrollX = newScrollX;
+                mScrollY = newScrollY;
+                return flingDone;
+            }
+        }
+        new ScrollDiffPollingCheck(webview).run();
+    }
+
     public void testPageScroll() throws Throwable {
         if (!NullWebViewUtils.isWebViewAvailable()) {
             return;
@@ -1578,29 +1609,29 @@
         }.run();
 
         do {
-            getInstrumentation().waitForIdleSync();
+            waitForFlingDone(mOnUiThread);
         } while (mOnUiThread.pageDown(false));
 
-        getInstrumentation().waitForIdleSync();
+        waitForFlingDone(mOnUiThread);
         int bottomScrollY = mOnUiThread.getScrollY();
 
         assertTrue(mOnUiThread.pageUp(false));
 
         do {
-            getInstrumentation().waitForIdleSync();
+            waitForFlingDone(mOnUiThread);
         } while (mOnUiThread.pageUp(false));
 
-        getInstrumentation().waitForIdleSync();
+        waitForFlingDone(mOnUiThread);
         int topScrollY = mOnUiThread.getScrollY();
 
         // jump to the bottom
         assertTrue(mOnUiThread.pageDown(true));
-        getInstrumentation().waitForIdleSync();
+        waitForFlingDone(mOnUiThread);
         assertEquals(bottomScrollY, mOnUiThread.getScrollY());
 
         // jump to the top
         assertTrue(mOnUiThread.pageUp(true));
-        getInstrumentation().waitForIdleSync();
+        waitForFlingDone(mOnUiThread);
         assertEquals(topScrollY, mOnUiThread.getScrollY());
     }
 
diff --git a/tests/tests/widget/res/layout/textview_layout.xml b/tests/tests/widget/res/layout/textview_layout.xml
index 419bbf9..bf7f757 100644
--- a/tests/tests/widget/res/layout/textview_layout.xml
+++ b/tests/tests/widget/res/layout/textview_layout.xml
@@ -27,6 +27,7 @@
                 android:layout_height="match_parent">
 
             <TextView android:id="@+id/textview_textAttr"
+                    android:fontFamily="@null"
                     android:text="@string/text_view_hello"
                     android:textColor="@drawable/black"
                     android:textColorHighlight="@drawable/yellow"
diff --git a/tests/tests/widget/src/android/widget/cts/MockPopupWindowCtsActivity.java b/tests/tests/widget/src/android/widget/cts/MockPopupWindowCtsActivity.java
index a68286a..41018a9 100644
--- a/tests/tests/widget/src/android/widget/cts/MockPopupWindowCtsActivity.java
+++ b/tests/tests/widget/src/android/widget/cts/MockPopupWindowCtsActivity.java
@@ -21,15 +21,37 @@
 import android.app.Activity;
 import android.os.Bundle;
 import android.widget.PopupWindow;
+import android.view.View;
+import android.view.View.OnApplyWindowInsetsListener;
+import android.view.Window;
+import android.view.WindowInsets;
 
 /**
  * Stub activity for testing {@link PopupWindow}
  */
 public class MockPopupWindowCtsActivity extends Activity {
+    private boolean isFirstRun = true;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        setContentView(R.layout.popupwindow);
+        Window window = getWindow();
+        final View decor = window.getDecorView();
+        decor.setOnApplyWindowInsetsListener(new OnApplyWindowInsetsListener() {
+            @Override
+            public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
+                if (isFirstRun) {
+                    if (insets.isRound()) {
+                        decor.setPadding(decor.getPaddingLeft(), decor.getPaddingTop(),
+                                decor.getPaddingRight(),
+                                decor.getPaddingBottom() + insets.getSystemWindowInsetBottom());
+                    }
+                    isFirstRun = false;
+                    setContentView(R.layout.popupwindow);
+                }
+                return insets.consumeSystemWindowInsets();
+            }
+        });
     }
 }
 
diff --git a/tests/tests/widget/src/android/widget/cts/TextViewTest.java b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
index 72193e7..480e1a6 100644
--- a/tests/tests/widget/src/android/widget/cts/TextViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
@@ -2046,8 +2046,14 @@
 
         assertEquals(SingleLineTransformationMethod.getInstance(),
                 textView.getTransformationMethod());
-        int singleLineWidth = textView.getLayout().getWidth();
-        int singleLineHeight = textView.getLayout().getHeight();
+
+        int singleLineWidth = 0;
+        int singleLineHeight = 0;
+
+        if (textView.getLayout() != null) {
+            singleLineWidth = textView.getLayout().getWidth();
+            singleLineHeight = textView.getLayout().getHeight();
+        }
 
         mActivity.runOnUiThread(new Runnable() {
             public void run() {
@@ -2056,8 +2062,11 @@
         });
         mInstrumentation.waitForIdleSync();
         assertEquals(null, textView.getTransformationMethod());
-        assertTrue(textView.getLayout().getHeight() > singleLineHeight);
-        assertTrue(textView.getLayout().getWidth() < singleLineWidth);
+
+        if (textView.getLayout() != null) {
+            assertTrue(textView.getLayout().getHeight() > singleLineHeight);
+            assertTrue(textView.getLayout().getWidth() < singleLineWidth);
+        }
 
         // same behaviours as setSingLine(true)
         mActivity.runOnUiThread(new Runnable() {
@@ -2068,8 +2077,11 @@
         mInstrumentation.waitForIdleSync();
         assertEquals(SingleLineTransformationMethod.getInstance(),
                 textView.getTransformationMethod());
-        assertEquals(singleLineHeight, textView.getLayout().getHeight());
-        assertEquals(singleLineWidth, textView.getLayout().getWidth());
+
+        if (textView.getLayout() != null) {
+            assertEquals(singleLineHeight, textView.getLayout().getHeight());
+            assertEquals(singleLineWidth, textView.getLayout().getWidth());
+        }
     }
 
     @UiThreadTest
@@ -2833,15 +2845,19 @@
         assertEquals(1, mTextView.getImeActionId());
     }
 
-    @UiThreadTest
     public void testSetTextLong() {
-        final int MAX_COUNT = 1 << 21;
-        char[] longText = new char[MAX_COUNT];
-        for (int n = 0; n < MAX_COUNT; n++) {
-            longText[n] = 'm';
-        }
-        mTextView = findTextView(R.id.textview_text);
-        mTextView.setText(new String(longText));
+        mActivity.runOnUiThread(new Runnable() {
+            public void run() {
+                final int MAX_COUNT = 1 << 21;
+                char[] longText = new char[MAX_COUNT];
+                for (int n = 0; n < MAX_COUNT; n++) {
+                    longText[n] = 'm';
+                }
+                mTextView = findTextView(R.id.textview_text);
+                mTextView.setText(new String(longText));
+            }
+        });
+        mInstrumentation.waitForIdleSync();
     }
 
     @UiThreadTest
diff --git a/tests/tests/widget/src/android/widget/cts/VideoViewTest.java b/tests/tests/widget/src/android/widget/cts/VideoViewTest.java
index 6e514f8..d9af514 100644
--- a/tests/tests/widget/src/android/widget/cts/VideoViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/VideoViewTest.java
@@ -21,6 +21,7 @@
 import android.app.Activity;
 import android.app.Instrumentation;
 import android.content.Context;
+import android.cts.util.MediaUtils;
 import android.cts.util.PollingCheck;
 import android.media.MediaCodecInfo;
 import android.media.MediaCodecList;
@@ -53,8 +54,6 @@
     private static final int    TEST_VIDEO_DURATION = 11047;
     /** The full name of R.raw.testvideo. */
     private static final String VIDEO_NAME   = "testvideo.3gp";
-    /** The MIME type. */
-    private static final String MIME_TYPE = "video/3gpp";
     /** delta for duration in case user uses different decoders on different
         hardware that report a duration that's different by a few milliseconds */
     private static final int DURATION_DELTA = 100;
@@ -101,26 +100,8 @@
         }
     }
 
-    // TODO: Make a public method selectCodec() in common libraries (e.g. cts/libs/), to avoid
-    // redundant function definitions in this and other media related test files.
-    private static boolean hasCodec(String mimeType) {
-        int numCodecs = MediaCodecList.getCodecCount();
-
-        for (int i = 0; i < numCodecs; i++) {
-            MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
-
-            if (!codecInfo.isEncoder()) {
-                continue;
-            }
-
-            String[] types = codecInfo.getSupportedTypes();
-            for (int j = 0; j < types.length; j++) {
-                if (types[j].equalsIgnoreCase(mimeType)) {
-                    return true;
-                }
-            }
-        }
-        return false;
+    private boolean hasCodec() {
+        return MediaUtils.hasCodecsForResource(mActivity, R.raw.testvideo);
     }
 
     /**
@@ -204,8 +185,8 @@
     public void testPlayVideo1() throws Throwable {
         makeVideoView();
         // Don't run the test if the codec isn't supported.
-        if (!hasCodec(MIME_TYPE)) {
-            Log.w(TAG, "Codec " + MIME_TYPE + " not supported. Return from testPlayVideo1.");
+        if (!hasCodec()) {
+            Log.i(TAG, "SKIPPING testPlayVideo1(): codec is not supported");
             return;
         }
 
@@ -266,8 +247,8 @@
     public void testGetBufferPercentage() throws Throwable {
         makeVideoView();
         // Don't run the test if the codec isn't supported.
-        if (!hasCodec(MIME_TYPE)) {
-            Log.w(TAG, MIME_TYPE + " not supported. Return from testGetBufferPercentage.");
+        if (!hasCodec()) {
+            Log.i(TAG, "SKIPPING testGetBufferPercentage(): codec is not supported");
             return;
         }
 
@@ -309,8 +290,8 @@
 
     public void testGetDuration() throws Throwable {
         // Don't run the test if the codec isn't supported.
-        if (!hasCodec(MIME_TYPE)) {
-            Log.w(TAG, "Codec " + MIME_TYPE + " not supported. Return from testGetDuration.");
+        if (!hasCodec()) {
+            Log.i(TAG, "SKIPPING testGetDuration(): codec is not supported");
             return;
         }
 
diff --git a/tools/cts-java-scanner/src/com/android/cts/javascanner/CtsJavaScanner.java b/tools/cts-java-scanner/src/com/android/cts/javascanner/CtsJavaScanner.java
index a843fc6..fc774e9 100644
--- a/tools/cts-java-scanner/src/com/android/cts/javascanner/CtsJavaScanner.java
+++ b/tools/cts-java-scanner/src/com/android/cts/javascanner/CtsJavaScanner.java
@@ -16,7 +16,9 @@
 package com.android.cts.javascanner;
 
 import java.io.File;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 
 /**
  * Class that searches a source directory for native gTests and outputs a
@@ -31,12 +33,12 @@
     }
 
     public static void main(String[] args) throws Exception {
-        File sourceDir = null;
+        List<File> sourceDirs = new ArrayList<File>();
         File docletPath = null;
 
         for (int i = 0; i < args.length; i++) {
             if ("-s".equals(args[i])) {
-                sourceDir = new File(getArg(args, ++i, "Missing value for source directory"));
+                sourceDirs.add(new File(getArg(args, ++i, "Missing value for source directory")));
             } else if ("-d".equals(args[i])) {
                 docletPath = new File(getArg(args, ++i, "Missing value for docletPath"));
             } else {
@@ -45,7 +47,7 @@
             }
         }
 
-        if (sourceDir == null) {
+        if (sourceDirs.isEmpty()) {
             System.err.println("Source directory is required");
             usage(args);
         }
@@ -55,7 +57,7 @@
             usage(args);
         }
 
-        DocletRunner runner = new DocletRunner(sourceDir, docletPath);
+        DocletRunner runner = new DocletRunner(sourceDirs, docletPath);
         System.exit(runner.runJavaDoc());
     }
 
diff --git a/tools/cts-java-scanner/src/com/android/cts/javascanner/DocletRunner.java b/tools/cts-java-scanner/src/com/android/cts/javascanner/DocletRunner.java
index 06951b9..94761fb 100644
--- a/tools/cts-java-scanner/src/com/android/cts/javascanner/DocletRunner.java
+++ b/tools/cts-java-scanner/src/com/android/cts/javascanner/DocletRunner.java
@@ -25,11 +25,11 @@
 
 class DocletRunner {
 
-    private final File mSourceDir;
+    private final List<File> mSourceDirs;
     private final File mDocletPath;
 
-    DocletRunner(File sourceDir, File docletPath) {
-        mSourceDir = sourceDir;
+    DocletRunner(List<File> sourceDirs, File docletPath) {
+        mSourceDirs = sourceDirs;
         mDocletPath = docletPath;
     }
 
@@ -41,10 +41,12 @@
         args.add("-docletpath");
         args.add(mDocletPath.toString());
         args.add("-sourcepath");
-        args.add(getSourcePath(mSourceDir));
+        args.add(getSourcePath(mSourceDirs));
         args.add("-classpath");
         args.add(getClassPath());
-        args.addAll(getSourceFiles(mSourceDir));
+        for (File sourceDir : mSourceDirs) {
+            args.addAll(getSourceFiles(sourceDir));
+        }
 
 
         // NOTE: We redirect the error stream to make sure the child process
@@ -67,7 +69,7 @@
         return process.waitFor();
     }
 
-    private String getSourcePath(File sourceDir) {
+    private String getSourcePath(List<File> sourceDirs) {
         List<String> sourcePath = new ArrayList<String>();
         sourcePath.add("./frameworks/base/core/java");
         sourcePath.add("./frameworks/base/test-runner/src");
@@ -77,7 +79,9 @@
         sourcePath.add("./cts/tests/src");
         sourcePath.add("./cts/libs/commonutil/src");
         sourcePath.add("./cts/libs/deviceutil/src");
-        sourcePath.add(sourceDir.toString());
+        for (File sourceDir : sourceDirs) {
+            sourcePath.add(sourceDir.toString());
+        }
         return join(sourcePath, ":");
     }
 
diff --git a/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoConstants.java b/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoConstants.java
index 1eb4acb..62c268d 100644
--- a/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoConstants.java
+++ b/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoConstants.java
@@ -68,4 +68,5 @@
     public static final String SERIAL_NUMBER = "deviceID";
     public static final String STORAGE_DEVICES = "storage_devices";
     public static final String MULTI_USER = "multi_user";
+    public static final String ENCRYPTED = "encrypted";
 }
diff --git a/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoInstrument.java b/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoInstrument.java
index 19349e5..52ddfe9 100644
--- a/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoInstrument.java
+++ b/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoInstrument.java
@@ -149,6 +149,9 @@
         // Multi-user support
         addResult(MULTI_USER, getMultiUserInfo());
 
+        // Encrypted
+        addResult(ENCRYPTED, getEncrypted());
+
         finish(Activity.RESULT_OK, mResults);
     }
 
@@ -394,4 +397,29 @@
 
         return "unknown";
     }
+
+    private static String getProperty(String property)
+            throws IOException {
+        Process process = new ProcessBuilder("getprop", property).start();
+        Scanner scanner = null;
+        String line = "";
+        try {
+            scanner = new Scanner(process.getInputStream());
+            line = scanner.nextLine();
+        } finally {
+            if (scanner != null) {
+                scanner.close();
+            }
+        }
+        return line;
+    }
+
+    private int getEncrypted() {
+        try {
+            return "encrypted".equals(getProperty("ro.crypto.state")) ? 1 : 0;
+        } catch (IOException e) {
+        }
+
+        return 0;
+    }
 }
diff --git a/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/RootProcessScanner.java b/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/RootProcessScanner.java
index d8018a1..01ca21b 100644
--- a/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/RootProcessScanner.java
+++ b/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/RootProcessScanner.java
@@ -29,12 +29,16 @@
     /** Processes that are allowed to run as root. */
     private static final Pattern ROOT_PROCESS_WHITELIST_PATTERN = getRootProcessWhitelistPattern(
             "debuggerd",
+            "debuggerd64",
+            "healthd",
             "init",
             "installd",
+            "lmkd",
             "netd",
             "servicemanager",
             "ueventd",
             "vold",
+            "watchdogd",
             "zygote"
     );
 
diff --git a/tools/selinux/SELinuxNeverallowTestFrame.py b/tools/selinux/SELinuxNeverallowTestFrame.py
new file mode 100644
index 0000000..932014a
--- /dev/null
+++ b/tools/selinux/SELinuxNeverallowTestFrame.py
@@ -0,0 +1,106 @@
+#!/usr/bin/python
+
+src_header = """/*
+ * 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.cts.security;
+
+import com.android.cts.tradefed.build.CtsBuildHelper;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.FileOutputStream;
+import java.lang.String;
+import java.net.URL;
+import java.util.Scanner;
+
+/**
+ * Neverallow Rules SELinux tests.
+ */
+public class SELinuxNeverallowRulesTest extends DeviceTestCase {
+    private File sepolicyAnalyze;
+    private File devicePolicyFile;
+
+    /**
+     * A reference to the device under test.
+     */
+    private ITestDevice mDevice;
+
+    private File copyResourceToTempFile(String resName) throws IOException {
+        InputStream is = this.getClass().getResourceAsStream(resName);
+        File tempFile = File.createTempFile("SELinuxHostTest", ".tmp");
+        FileOutputStream os = new FileOutputStream(tempFile);
+        int rByte = 0;
+        while ((rByte = is.read()) != -1) {
+            os.write(rByte);
+        }
+        os.flush();
+        os.close();
+        tempFile.deleteOnExit();
+        return tempFile;
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mDevice = getDevice();
+
+        /* retrieve the sepolicy-analyze executable from jar */
+        sepolicyAnalyze = copyResourceToTempFile("/sepolicy-analyze");
+        sepolicyAnalyze.setExecutable(true);
+
+        /* obtain sepolicy file from running device */
+        devicePolicyFile = File.createTempFile("sepolicy", ".tmp");
+        devicePolicyFile.deleteOnExit();
+        mDevice.executeAdbCommand("pull", "/sys/fs/selinux/policy",
+                devicePolicyFile.getAbsolutePath());
+    }
+"""
+src_body = ""
+src_footer = """}
+"""
+
+src_method = """
+    public void testNeverallowRules() throws Exception {
+        String neverallowRule = "$NEVERALLOW_RULE_HERE$";
+
+        /* run sepolicy-analyze neverallow check on policy file using given neverallow rules */
+        ProcessBuilder pb = new ProcessBuilder(sepolicyAnalyze.getAbsolutePath(),
+                devicePolicyFile.getAbsolutePath(), "neverallow", "-n",
+                neverallowRule);
+        pb.redirectOutput(ProcessBuilder.Redirect.PIPE);
+        pb.redirectErrorStream(true);
+        Process p = pb.start();
+        p.waitFor();
+        BufferedReader result = new BufferedReader(new InputStreamReader(p.getInputStream()));
+        String line;
+        StringBuilder errorString = new StringBuilder();
+        while ((line = result.readLine()) != null) {
+            errorString.append(line);
+            errorString.append("\\n");
+        }
+        assertTrue("The following errors were encountered when validating the SELinux"
+                   + "neverallow rule:\\n" + neverallowRule + "\\n" + errorString,
+                   errorString.length() == 0);
+    }
+"""
diff --git a/tools/selinux/SELinuxNeverallowTestGen.py b/tools/selinux/SELinuxNeverallowTestGen.py
new file mode 100755
index 0000000..9cb1e24
--- /dev/null
+++ b/tools/selinux/SELinuxNeverallowTestGen.py
@@ -0,0 +1,53 @@
+#!/usr/bin/python
+
+import re
+import sys
+import SELinuxNeverallowTestFrame
+
+usage = "Usage: ./gen_SELinux_CTS_neverallows.py <input policy file> <output cts java source>"
+
+# extract_neverallow_rules - takes an intermediate policy file and pulls out the
+# neverallow rules by taking all of the non-commented text between the 'neverallow'
+# keyword and a terminating ';'
+# returns: a list of strings representing these rules
+def extract_neverallow_rules(policy_file):
+    with open(policy_file, 'r') as in_file:
+        policy_str = in_file.read()
+        # remove comments
+        no_comments = re.sub(r'#.+?$', r'', policy_str, flags = re.M)
+        # match neverallow rules
+        return re.findall(r'(^neverallow\s.+?;)', no_comments, flags = re.M |re.S);
+
+# neverallow_rule_to_test - takes a neverallow statement and transforms it into
+# the output necessary to form a cts unit test in a java source file.
+# returns: a string representing a generic test method based on this rule.
+def neverallow_rule_to_test(neverallow_rule, test_num):
+    squashed_neverallow = neverallow_rule.replace("\n", " ")
+    method  = SELinuxNeverallowTestFrame.src_method
+    method = method.replace("testNeverallowRules()",
+        "testNeverallowRules" + str(test_num) + "()")
+    return method.replace("$NEVERALLOW_RULE_HERE$", squashed_neverallow)
+
+if __name__ == "__main__":
+    # check usage
+    if len(sys.argv) != 3:
+        print usage
+        exit()
+    input_file = sys.argv[1]
+    output_file = sys.argv[2]
+
+    src_header = SELinuxNeverallowTestFrame.src_header
+    src_body = SELinuxNeverallowTestFrame.src_body
+    src_footer = SELinuxNeverallowTestFrame.src_footer
+
+    # grab the neverallow rules from the policy file and transform into tests
+    neverallow_rules = extract_neverallow_rules(input_file)
+    i = 0
+    for rule in neverallow_rules:
+        src_body += neverallow_rule_to_test(rule, i)
+        i += 1
+
+    with open(output_file, 'w') as out_file:
+        out_file.write(src_header)
+        out_file.write(src_body)
+        out_file.write(src_footer)
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildProvider.java b/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildProvider.java
index 25431b2..c43183a 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildProvider.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildProvider.java
@@ -31,7 +31,7 @@
     @Option(name="cts-install-path", description="the path to the cts installation to use")
     private String mCtsRootDirPath = System.getProperty("CTS_ROOT");
 
-    public static final String CTS_BUILD_VERSION = "5.0_r2";
+    public static final String CTS_BUILD_VERSION = "5.0_r2.5";
 
     /**
      * {@inheritDoc}