Merge \\\\\\"Merge \\\\\\\"DO NOT MERGE: Reduce CTS audio requirements for low memory devices\\\\\\\" into lollipop-mr1-cts-dev am: fec1a08bba  -s ours\\\\\\" into marshmallow-cts-dev am: 08bcb89e03  -s ours am: c752176cb3 am: 485c050dd8 am: 608685f969 am: 03f4e1d2df  -s ours
am: 085dbd960d

Change-Id: Ib8008ab2a4446101ce07a75ee18ab784b135e994
diff --git a/apps/CameraITS/pymodules/its/image.py b/apps/CameraITS/pymodules/its/image.py
index a5ac60b..0057eb7 100644
--- a/apps/CameraITS/pymodules/its/image.py
+++ b/apps/CameraITS/pymodules/its/image.py
@@ -366,8 +366,7 @@
 
     # Reorder black levels and gains to R,Gr,Gb,B, to match the order
     # of the planes.
-    idxs = get_canonical_cfa_order(props)
-    black_levels = [black_levels[i] for i in idxs]
+    black_levels = [get_black_level(i,props,cap_res) for i in range(4)]
     gains = get_gains_in_canonical_order(props, gains)
 
     # Convert CCM from rational to float, as numpy arrays.
@@ -390,6 +389,28 @@
     img = numpy.dot(img.reshape(w*h,3), ccm.T).reshape(h,w,3).clip(0.0,1.0)
     return img
 
+def get_black_level(chan, props, cap_res):
+    """Return the black level to use for a given capture.
+
+    Uses a dynamic value from the capture result if available, else falls back
+    to the static global value in the camera characteristics.
+
+    Args:
+        chan: The channel index, in canonical order (R, Gr, Gb, B).
+        props: The camera properties object.
+        cap_res: A capture result object.
+
+    Returns:
+        The black level value for the specified channel.
+    """
+    if cap_res.has_key("android.sensor.dynamicBlackLevel"):
+        black_levels = cap_res["android.sensor.dynamicBlackLevel"]
+    else:
+        black_levels = props['android.sensor.blackLevelPattern']
+    idxs = its.image.get_canonical_cfa_order(props)
+    ordered_black_levels = [black_levels[i] for i in idxs]
+    return ordered_black_levels[chan]
+
 def convert_yuv420_planar_to_rgb_image(y_plane, u_plane, v_plane,
                                        w, h,
                                        ccm_yuv_to_rgb=DEFAULT_YUV_TO_RGB_CCM,
diff --git a/apps/CameraITS/pymodules/its/objects.py b/apps/CameraITS/pymodules/its/objects.py
index ac384fb..58fe4ec 100644
--- a/apps/CameraITS/pymodules/its/objects.py
+++ b/apps/CameraITS/pymodules/its/objects.py
@@ -71,7 +71,7 @@
         return float(r["numerator"]) / float(r["denominator"])
 
 def manual_capture_request(
-        sensitivity, exp_time, linear_tonemap=False, props=None):
+        sensitivity, exp_time, f_distance = 0.0, linear_tonemap=False, props=None):
     """Return a capture request with everything set to manual.
 
     Uses identity/unit color correction, and the default tonemap curve.
@@ -81,6 +81,7 @@
         sensitivity: The sensitivity value to populate the request with.
         exp_time: The exposure time, in nanoseconds, to populate the request
             with.
+        f_distance: The focus distance to populate the request with.
         linear_tonemap: [Optional] whether a linear tonemap should be used
             in this request.
         props: [Optional] the object returned from
@@ -105,6 +106,7 @@
         "android.colorCorrection.transform":
                 int_to_rational([1,0,0, 0,1,0, 0,0,1]),
         "android.colorCorrection.gains": [1,1,1,1],
+        "android.lens.focusDistance" : f_distance,
         "android.tonemap.mode": 1,
         "android.shading.mode": 1
         }
diff --git a/apps/CameraITS/tests/dng_noise_model/dng_noise_model.py b/apps/CameraITS/tests/dng_noise_model/dng_noise_model.py
index 8f4682a..e86ebd2 100644
--- a/apps/CameraITS/tests/dng_noise_model/dng_noise_model.py
+++ b/apps/CameraITS/tests/dng_noise_model/dng_noise_model.py
@@ -86,9 +86,6 @@
         sens_min, sens_max = props['android.sensor.info.sensitivityRange']
         sens_max_analog = props['android.sensor.maxAnalogSensitivity']
         white_level = props['android.sensor.info.whiteLevel']
-        black_levels = props['android.sensor.blackLevelPattern']
-        idxs = its.image.get_canonical_cfa_order(props)
-        black_levels = [black_levels[i] for i in idxs]
 
         print "Sensitivity range: [%f, %f]" % (sens_min, sens_max)
         print "Max analog sensitivity: %f" % (sens_max_analog)
@@ -138,13 +135,14 @@
                     p = p.squeeze()
 
                     # Crop the plane to be a multiple of the tile size.
-                    p = p[0:p.shape[0] - p.shape[0]%tile_size, 
+                    p = p[0:p.shape[0] - p.shape[0]%tile_size,
                           0:p.shape[1] - p.shape[1]%tile_size]
 
                     # convert_capture_to_planes normalizes the range
                     # to [0, 1], but without subtracting the black
                     # level.
-                    black_level = black_levels[pidx]
+                    black_level = its.image.get_black_level(
+                        pidx, props, cap["metadata"])
                     p = p*white_level
                     p = (p - black_level)/(white_level - black_level)
 
diff --git a/apps/CameraITS/tests/scene0/test_metadata.py b/apps/CameraITS/tests/scene0/test_metadata.py
index 2914493..ed1426b 100644
--- a/apps/CameraITS/tests/scene0/test_metadata.py
+++ b/apps/CameraITS/tests/scene0/test_metadata.py
@@ -12,11 +12,13 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import its.image
+import math
+
+import its.caps
 import its.device
 import its.objects
 import its.target
-import its.caps
+
 
 def main():
     """Test the validity of some metadata entries.
@@ -75,6 +77,31 @@
 
     assert(not failed)
 
+    if not its.caps.legacy(props):
+        # Test: pixel_pitch, FOV, and hyperfocal distance are reasonable
+        fmts = props["android.scaler.streamConfigurationMap"]["availableStreamConfigurations"]
+        fmts = sorted(fmts, key=lambda k: k["width"]*k["height"], reverse=True)
+        sensor_size = props["android.sensor.info.physicalSize"]
+        pixel_pitch_h = (sensor_size["height"] / fmts[0]["height"] * 1E3)
+        pixel_pitch_w = (sensor_size["width"] / fmts[0]["width"] * 1E3)
+        print "Assert pixel_pitch WxH: %.2f um, %.2f um" % (pixel_pitch_w,
+                                                            pixel_pitch_h)
+        assert 1.0 <= pixel_pitch_w <= 10
+        assert 1.0 <= pixel_pitch_h <= 10
+        assert 0.333 <= pixel_pitch_w/pixel_pitch_h <= 3.0
+
+        diag = math.sqrt(sensor_size["height"] ** 2 +
+                         sensor_size["width"] ** 2)
+        fl = md["android.lens.focalLength"]
+        fov = 2 * math.degrees(math.atan(diag / (2 * fl)))
+        print "Assert field of view: %.1f degrees" % fov
+        assert 30 <= fov <= 130
+
+        hyperfocal = 1.0 / props["android.lens.info.hyperfocalDistance"]
+        print "Assert hyperfocal distance: %.2f m" % hyperfocal
+        assert 0.02 <= hyperfocal
+
+
 def getval(expr, default=None):
     try:
         return eval(expr)
@@ -82,6 +109,8 @@
         return default
 
 failed = False
+
+
 def check(expr):
     global md, props, failed
     try:
diff --git a/apps/CameraITS/tests/scene1/test_auto_vs_manual.py b/apps/CameraITS/tests/scene1/test_auto_vs_manual.py
index a9efa0b..c5f5e29 100644
--- a/apps/CameraITS/tests/scene1/test_auto_vs_manual.py
+++ b/apps/CameraITS/tests/scene1/test_auto_vs_manual.py
@@ -56,7 +56,7 @@
         print "Auto transform:", xform_a
 
         # Manual capture 1: WB
-        req = its.objects.manual_capture_request(sens, exp)
+        req = its.objects.manual_capture_request(sens, exp, focus)
         req["android.colorCorrection.transform"] = xform_rat
         req["android.colorCorrection.gains"] = gains
         cap_man1 = cam.do_capture(req)
diff --git a/apps/CameraITS/tests/scene1/test_crop_region_raw.py b/apps/CameraITS/tests/scene1/test_crop_region_raw.py
index 7973755..f2d788e 100644
--- a/apps/CameraITS/tests/scene1/test_crop_region_raw.py
+++ b/apps/CameraITS/tests/scene1/test_crop_region_raw.py
@@ -64,7 +64,7 @@
         # 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).
         e, s = its.target.get_target_exposure_combos(cam)["minSensitivity"]
-        req = its.objects.manual_capture_request(s,e, True, props)
+        req = its.objects.manual_capture_request(s,e, 0.0, True, props)
         cap1_raw, cap1_yuv = cam.do_capture(req, cam.CAP_RAW_YUV)
 
         # Capture with a crop region.
diff --git a/apps/CameraITS/tests/scene1/test_dng_noise_model.py b/apps/CameraITS/tests/scene1/test_dng_noise_model.py
index 51270b6..b7ba0e8 100644
--- a/apps/CameraITS/tests/scene1/test_dng_noise_model.py
+++ b/apps/CameraITS/tests/scene1/test_dng_noise_model.py
@@ -47,14 +47,12 @@
                              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_ae,e_ae,_,_,f_dist  = cam.do_3a(get_results=True)
         s_e_prod = s_ae * e_ae
         sensitivities = range(sens_min, sens_max, sens_step)
 
@@ -64,7 +62,7 @@
 
             # Capture a raw frame with the desired sensitivity.
             exp = int(s_e_prod / float(sens))
-            req = its.objects.manual_capture_request(sens, exp)
+            req = its.objects.manual_capture_request(sens, exp, f_dist)
             cap = cam.do_capture(req, cam.CAP_RAW)
 
             # Test each raw color channel (R, GR, GB, B):
@@ -79,8 +77,10 @@
                 # 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])
+                black_level = its.image.get_black_level(
+                    ch, props, cap["metadata"])
+                plane = (plane * white_level - black_level) / (
+                    white_level - black_level)
                 tile = its.image.get_image_patch(plane, 0.49,0.49,0.02,0.02)
                 mean = tile.mean()
 
diff --git a/apps/CameraITS/tests/scene1/test_exposure.py b/apps/CameraITS/tests/scene1/test_exposure.py
index dc4a790..6299f79 100644
--- a/apps/CameraITS/tests/scene1/test_exposure.py
+++ b/apps/CameraITS/tests/scene1/test_exposure.py
@@ -62,7 +62,8 @@
             s_test = round(s*m)
             e_test = s_e_product / s_test
             print "Testing s:", s_test, "e:", e_test
-            req = its.objects.manual_capture_request(s_test, e_test, True, props)
+            req = its.objects.manual_capture_request(
+                    s_test, e_test, 0.0, True, props)
             cap = cam.do_capture(req)
             s_res = cap["metadata"]["android.sensor.sensitivity"]
             e_res = cap["metadata"]["android.sensor.exposureTime"]
diff --git a/apps/CameraITS/tests/scene1/test_jpeg.py b/apps/CameraITS/tests/scene1/test_jpeg.py
index 7bc038d..6b14411 100644
--- a/apps/CameraITS/tests/scene1/test_jpeg.py
+++ b/apps/CameraITS/tests/scene1/test_jpeg.py
@@ -33,7 +33,7 @@
                              its.caps.per_frame_control(props))
 
         e, s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
-        req = its.objects.manual_capture_request(s, e, True, props)
+        req = its.objects.manual_capture_request(s, e, 0.0, True, props)
 
         # YUV
         size = its.objects.get_available_output_sizes("yuv", props)[0]
diff --git a/apps/CameraITS/tests/scene1/test_latching.py b/apps/CameraITS/tests/scene1/test_latching.py
index a7da421..6e42c23 100644
--- a/apps/CameraITS/tests/scene1/test_latching.py
+++ b/apps/CameraITS/tests/scene1/test_latching.py
@@ -44,20 +44,20 @@
         b_means = []
 
         reqs = [
-            its.objects.manual_capture_request(s,  e,   True, props),
-            its.objects.manual_capture_request(s,  e,   True, props),
-            its.objects.manual_capture_request(s*2,e,   True, props),
-            its.objects.manual_capture_request(s*2,e,   True, props),
-            its.objects.manual_capture_request(s,  e,   True, props),
-            its.objects.manual_capture_request(s,  e,   True, props),
-            its.objects.manual_capture_request(s,  e*2, True, props),
-            its.objects.manual_capture_request(s,  e,   True, props),
-            its.objects.manual_capture_request(s*2,e,   True, props),
-            its.objects.manual_capture_request(s,  e,   True, props),
-            its.objects.manual_capture_request(s,  e*2, True, props),
-            its.objects.manual_capture_request(s,  e,   True, props),
-            its.objects.manual_capture_request(s,  e*2, True, props),
-            its.objects.manual_capture_request(s,  e*2, True, props),
+            its.objects.manual_capture_request(s,  e,   0.0, True, props),
+            its.objects.manual_capture_request(s,  e,   0.0, True, props),
+            its.objects.manual_capture_request(s*2,e,   0.0, True, props),
+            its.objects.manual_capture_request(s*2,e,   0.0, True, props),
+            its.objects.manual_capture_request(s,  e,   0.0, True, props),
+            its.objects.manual_capture_request(s,  e,   0.0, True, props),
+            its.objects.manual_capture_request(s,  e*2, 0.0, True, props),
+            its.objects.manual_capture_request(s,  e,   0.0, True, props),
+            its.objects.manual_capture_request(s*2,e,   0.0, True, props),
+            its.objects.manual_capture_request(s,  e,   0.0, True, props),
+            its.objects.manual_capture_request(s,  e*2, 0.0, True, props),
+            its.objects.manual_capture_request(s,  e,   0.0, True, props),
+            its.objects.manual_capture_request(s,  e*2, 0.0, True, props),
+            its.objects.manual_capture_request(s,  e*2, 0.0, True, props),
             ]
 
         caps = cam.do_capture(reqs, fmt)
diff --git a/apps/CameraITS/tests/scene1/test_param_color_correction.py b/apps/CameraITS/tests/scene1/test_param_color_correction.py
index 09b3707..8623426 100644
--- a/apps/CameraITS/tests/scene1/test_param_color_correction.py
+++ b/apps/CameraITS/tests/scene1/test_param_color_correction.py
@@ -42,7 +42,7 @@
 
         # Baseline request
         e, s = its.target.get_target_exposure_combos(cam)["midSensitivity"]
-        req = its.objects.manual_capture_request(s, e, True, props)
+        req = its.objects.manual_capture_request(s, e, 0.0, True, props)
         req["android.colorCorrection.mode"] = 0
 
         # Transforms:
diff --git a/apps/CameraITS/tests/scene1/test_param_exposure_time.py b/apps/CameraITS/tests/scene1/test_param_exposure_time.py
index 0c0aab1..576516c 100644
--- a/apps/CameraITS/tests/scene1/test_param_exposure_time.py
+++ b/apps/CameraITS/tests/scene1/test_param_exposure_time.py
@@ -39,7 +39,7 @@
 
         e,s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
         for i,e_mult in enumerate([0.8, 0.9, 1.0, 1.1, 1.2]):
-            req = its.objects.manual_capture_request(s, e * e_mult, True, props)
+            req = its.objects.manual_capture_request(s, e * e_mult, 0.0, True, props)
             cap = cam.do_capture(req)
             img = its.image.convert_capture_to_rgb_image(cap)
             its.image.write_image(
diff --git a/apps/CameraITS/tests/scene1/test_param_flash_mode.py b/apps/CameraITS/tests/scene1/test_param_flash_mode.py
index 38f864f..5ef6fd6 100644
--- a/apps/CameraITS/tests/scene1/test_param_flash_mode.py
+++ b/apps/CameraITS/tests/scene1/test_param_flash_mode.py
@@ -39,7 +39,7 @@
         # linear tonemap.
         e, s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
         e /= 4
-        req = its.objects.manual_capture_request(s, e, True, props)
+        req = its.objects.manual_capture_request(s, e, 0.0, True, props)
 
         for f in [0,1,2]:
             req["android.flash.mode"] = f
diff --git a/apps/CameraITS/tests/scene1/test_raw_burst_sensitivity.py b/apps/CameraITS/tests/scene1/test_raw_burst_sensitivity.py
index e176312..1d2a6b1 100644
--- a/apps/CameraITS/tests/scene1/test_raw_burst_sensitivity.py
+++ b/apps/CameraITS/tests/scene1/test_raw_burst_sensitivity.py
@@ -47,14 +47,14 @@
         # Digital gains might not be visible on RAW data
         sens_max = props['android.sensor.maxAnalogSensitivity']
         sens_step = (sens_max - sens_min) / NUM_STEPS
-        s_ae,e_ae,_,_,_  = cam.do_3a(get_results=True)
+        s_ae,e_ae,_,_,f_dist  = cam.do_3a(get_results=True)
         s_e_prod = s_ae * e_ae
 
         reqs = []
         settings = []
         for s in range(sens_min, sens_max, sens_step):
             e = int(s_e_prod / float(s))
-            req = its.objects.manual_capture_request(s, e)
+            req = its.objects.manual_capture_request(s, e, f_dist)
             reqs.append(req)
             settings.append((s,e))
 
diff --git a/apps/CameraITS/tests/scene1/test_raw_sensitivity.py b/apps/CameraITS/tests/scene1/test_raw_sensitivity.py
index cc0ce14..e49ee34 100644
--- a/apps/CameraITS/tests/scene1/test_raw_sensitivity.py
+++ b/apps/CameraITS/tests/scene1/test_raw_sensitivity.py
@@ -45,14 +45,14 @@
         # Digital gains might not be visible on RAW data
         sens_max = props['android.sensor.maxAnalogSensitivity']
         sens_step = (sens_max - sens_min) / NUM_STEPS
-        s_ae,e_ae,_,_,_  = cam.do_3a(get_results=True)
+        s_ae,e_ae,_,_,f_dist  = cam.do_3a(get_results=True)
         s_e_prod = s_ae * e_ae
 
         variances = []
         for s in range(sens_min, sens_max, sens_step):
 
             e = int(s_e_prod / float(s))
-            req = its.objects.manual_capture_request(s, e)
+            req = its.objects.manual_capture_request(s, e, f_dist)
 
             # Capture raw+yuv, but only look at the raw.
             cap,_ = cam.do_capture(req, cam.CAP_RAW_YUV)
diff --git a/apps/CameraITS/tests/scene1/test_tonemap_sequence.py b/apps/CameraITS/tests/scene1/test_tonemap_sequence.py
index 54d3d65..0db70b8 100644
--- a/apps/CameraITS/tests/scene1/test_tonemap_sequence.py
+++ b/apps/CameraITS/tests/scene1/test_tonemap_sequence.py
@@ -35,12 +35,12 @@
                              its.caps.manual_post_proc(props) and
                              its.caps.per_frame_control(props))
 
-        sens, exp_time, _,_,_ = cam.do_3a(do_af=False,get_results=True)
+        sens, exp_time, _,_,f_dist = cam.do_3a(do_af=True,get_results=True)
 
         means = []
 
         # Capture 3 manual shots with a linear tonemap.
-        req = its.objects.manual_capture_request(sens, exp_time, True, props)
+        req = its.objects.manual_capture_request(sens, exp_time, f_dist, True, props)
         for i in [0,1,2]:
             cap = cam.do_capture(req)
             img = its.image.convert_capture_to_rgb_image(cap)
@@ -49,7 +49,7 @@
             means.append(tile.mean(0).mean(0))
 
         # Capture 3 manual shots with the default tonemap.
-        req = its.objects.manual_capture_request(sens, exp_time, False)
+        req = its.objects.manual_capture_request(sens, exp_time, f_dist, False)
         for i in [3,4,5]:
             cap = cam.do_capture(req)
             img = its.image.convert_capture_to_rgb_image(cap)
diff --git a/apps/CameraITS/tests/scene1/test_yuv_jpeg_all.py b/apps/CameraITS/tests/scene1/test_yuv_jpeg_all.py
index 0c428fc..0d3726d 100644
--- a/apps/CameraITS/tests/scene1/test_yuv_jpeg_all.py
+++ b/apps/CameraITS/tests/scene1/test_yuv_jpeg_all.py
@@ -35,7 +35,7 @@
         # Use a manual request with a linear tonemap so that the YUV and JPEG
         # should look the same (once converted by the its.image module).
         e, s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
-        req = its.objects.manual_capture_request(s, e, True, props)
+        req = its.objects.manual_capture_request(s, e, 0.0, True, props)
 
         rgbs = []
 
diff --git a/apps/CameraITS/tests/scene1/test_yuv_plus_jpeg.py b/apps/CameraITS/tests/scene1/test_yuv_plus_jpeg.py
index 78378eb..f559c2b 100644
--- a/apps/CameraITS/tests/scene1/test_yuv_plus_jpeg.py
+++ b/apps/CameraITS/tests/scene1/test_yuv_plus_jpeg.py
@@ -41,7 +41,7 @@
         # Use a manual request with a linear tonemap so that the YUV and JPEG
         # should look the same (once converted by the its.image module).
         e, s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
-        req = its.objects.manual_capture_request(s, e, True, props)
+        req = its.objects.manual_capture_request(s, e, 0.0, True, props)
 
         cap_yuv, cap_jpeg = cam.do_capture(req, [fmt_yuv, fmt_jpeg])
 
diff --git a/apps/CameraITS/tests/scene1/test_yuv_plus_raw.py b/apps/CameraITS/tests/scene1/test_yuv_plus_raw.py
index bfa6a28..a5ceaba 100644
--- a/apps/CameraITS/tests/scene1/test_yuv_plus_raw.py
+++ b/apps/CameraITS/tests/scene1/test_yuv_plus_raw.py
@@ -36,7 +36,7 @@
         # 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).
         e, s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
-        req = its.objects.manual_capture_request(s, e, True, props)
+        req = its.objects.manual_capture_request(s, e, 0.0, True, props)
 
         max_raw_size = \
                 its.objects.get_available_output_sizes("raw", props)[0]
diff --git a/apps/CameraITS/tests/scene1/test_yuv_plus_raw10.py b/apps/CameraITS/tests/scene1/test_yuv_plus_raw10.py
index 322af10..f281089 100644
--- a/apps/CameraITS/tests/scene1/test_yuv_plus_raw10.py
+++ b/apps/CameraITS/tests/scene1/test_yuv_plus_raw10.py
@@ -36,7 +36,7 @@
         # 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).
         e, s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
-        req = its.objects.manual_capture_request(s, e, True, props)
+        req = its.objects.manual_capture_request(s, e, 0.0, True, props)
 
         max_raw10_size = \
                 its.objects.get_available_output_sizes("raw10", props)[0]
diff --git a/apps/CameraITS/tests/scene1/test_yuv_plus_raw12.py b/apps/CameraITS/tests/scene1/test_yuv_plus_raw12.py
index b3cca0b..5b6051a 100644
--- a/apps/CameraITS/tests/scene1/test_yuv_plus_raw12.py
+++ b/apps/CameraITS/tests/scene1/test_yuv_plus_raw12.py
@@ -36,7 +36,7 @@
         # 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).
         e, s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
-        req = its.objects.manual_capture_request(s, e, True, props)
+        req = its.objects.manual_capture_request(s, e, 0.0, True, props)
 
         max_raw12_size = \
                 its.objects.get_available_output_sizes("raw12", props)[0]
diff --git a/apps/CameraITS/tests/scene4/test_aspect_ratio_and_crop.py b/apps/CameraITS/tests/scene4/test_aspect_ratio_and_crop.py
index a5fc819..ec9d6e8 100644
--- a/apps/CameraITS/tests/scene4/test_aspect_ratio_and_crop.py
+++ b/apps/CameraITS/tests/scene4/test_aspect_ratio_and_crop.py
@@ -45,6 +45,8 @@
     THRES_L_CP_TEST = 0.02
     # pass/fail threshold of mini size images for crop test
     THRES_XS_CP_TEST = 0.05
+    # Crop test will allow at least THRES_MIN_PIXEL offset
+    THRES_MIN_PIXEL = 4
     PREVIEW_SIZE = (1920, 1080) # preview size
     aspect_ratio_gt = 1  # ground truth
     failed_ar = []  # streams failed the aspect ration test
@@ -84,7 +86,7 @@
         print "AWB transform", xform
         print "AF distance", focus
         req = its.objects.manual_capture_request(
-                sens, exp, True, props)
+                sens, exp, focus, True, props)
         xform_rat = its.objects.float_to_rational(xform)
         req["android.colorCorrection.gains"] = gains
         req["android.colorCorrection.transform"] = xform_rat
@@ -150,8 +152,8 @@
                 img = its.image.convert_capture_to_rgb_image(frm_iter)
                 img_name = "%s_%s_with_%s_w%d_h%d.png" \
                            % (NAME, fmt_iter, fmt_cmpr, w_iter, h_iter)
-                aspect_ratio, cc_ct, _ = measure_aspect_ratio(img, raw_avlb,
-                                                              img_name)
+                aspect_ratio, cc_ct, (cc_w, cc_h) = \
+                        measure_aspect_ratio(img, raw_avlb, img_name)
                 # check pass/fail for aspect ratio
                 # image size >= LARGE_SIZE: use THRES_L_AR_TEST
                 # image size == 0 (extreme case): THRES_XS_AR_TEST
@@ -176,14 +178,23 @@
                     # image size == 0 (extreme case): thres_xs_cp_test
                     # 0 < image size < LARGE_SIZE: scale between
                     # thres_xs_cp_test and thres_l_cp_test
+                    # Also, allow at least THRES_MIN_PIXEL off to
+                    # prevent threshold being too tight for very
+                    # small circle
                     thres_hori_cp_test = max(thres_l_cp_test,
                             thres_xs_cp_test + w_iter *
                             (thres_l_cp_test-thres_xs_cp_test)/LARGE_SIZE)
+                    min_threshold_h = THRES_MIN_PIXEL / cc_w
+                    thres_hori_cp_test = max(thres_hori_cp_test,
+                            min_threshold_h)
                     thres_range_h_cp = (cc_ct_gt["hori"]-thres_hori_cp_test,
                                         cc_ct_gt["hori"]+thres_hori_cp_test)
                     thres_vert_cp_test = max(thres_l_cp_test,
                             thres_xs_cp_test + h_iter *
                             (thres_l_cp_test-thres_xs_cp_test)/LARGE_SIZE)
+                    min_threshold_v = THRES_MIN_PIXEL / cc_h
+                    thres_vert_cp_test = max(thres_vert_cp_test,
+                            min_threshold_v)
                     thres_range_v_cp = (cc_ct_gt["vert"]-thres_vert_cp_test,
                                         cc_ct_gt["vert"]+thres_vert_cp_test)
                     if cc_ct["hori"] < thres_range_h_cp[0] \
diff --git a/apps/CameraITS/tools/run_all_tests.py b/apps/CameraITS/tools/run_all_tests.py
index 301ea73..52780eb 100644
--- a/apps/CameraITS/tools/run_all_tests.py
+++ b/apps/CameraITS/tools/run_all_tests.py
@@ -51,7 +51,8 @@
 
     # Get all the scene0 and scene1 tests, which can be run using the same
     # physical setup.
-    scenes = ["scene0", "scene1", "scene2", "scene3", "scene4", "scene5"]
+    scenes = ["scene0", "scene1", "scene2", "scene3", "scene4", "scene5",
+              "sensor_fusion"]
 
     scene_req = {
         "scene0" : None,
diff --git a/apps/CtsVerifier/Android.mk b/apps/CtsVerifier/Android.mk
index a52ea7a..304c982 100644
--- a/apps/CtsVerifier/Android.mk
+++ b/apps/CtsVerifier/Android.mk
@@ -42,7 +42,9 @@
 
 LOCAL_PACKAGE_NAME := CtsVerifier
 
-LOCAL_JNI_SHARED_LIBRARIES := libctsverifier_jni libaudioloopback_jni
+LOCAL_JNI_SHARED_LIBRARIES := libctsverifier_jni \
+		libaudioloopback_jni \
+		libnativehelper_compat_libc++
 
 LOCAL_PROGUARD_FLAG_FILES := proguard.flags
 
diff --git a/apps/CtsVerifier/jni/verifier/Android.mk b/apps/CtsVerifier/jni/verifier/Android.mk
index 4840e62..2350085 100644
--- a/apps/CtsVerifier/jni/verifier/Android.mk
+++ b/apps/CtsVerifier/jni/verifier/Android.mk
@@ -21,8 +21,6 @@
 
 LOCAL_MODULE_TAGS := optional
 
-
-
 LOCAL_SRC_FILES := \
 		CtsVerifierJniOnLoad.cpp \
 		com_android_cts_verifier_camera_StatsImage.cpp \
@@ -30,6 +28,10 @@
 
 LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
 
-LOCAL_SHARED_LIBRARIES := liblog
+LOCAL_CXX_STL := libc++_static
+
+LOCAL_SHARED_LIBRARIES := liblog \
+		libnativehelper_compat_libc++
 
 include $(BUILD_SHARED_LIBRARY)
+
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 52da8c4..ef77c84 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -2217,8 +2217,7 @@
     <string name="device_owner_disallow_config_wifi_info">
         Please press the Set restriction button to set the user restriction.
         Then press Go to open the WiFi page in Settings.
-        Confirm that:\n
-        \n
+        Confirm that:\n\n
         - You cannot view WiFi networks in range.\n
         - Trying to edit, add or remove any existing WiFi configs triggers a support message.\n
         \n
@@ -2229,13 +2228,24 @@
         Device should have a sim card to perform this test.
         Please press the Set restriction button to set the user restriction.
         Then press Go to open the Cellular network page in Settings.
-        Confirm that:\n
-        \n
+        Confirm that:\n\n
         - Data roaming is disabled.\n
-        - Enabling data roaming is not possible and triggers a support message.\n
-        \n
+        - Enabling data roaming is not possible and triggers a support message.\n\n
         Use the Back button to return to this page.
     </string>
+    <string name="device_owner_disallow_factory_reset">Disallow factory reset</string>
+    <string name="device_owner_disallow_factory_reset_info">
+        Please press the Set button to set the user restriction.\n
+        1. Go to the factory reset settings. It is often located in \"Backup &amp; reset\" settings.\n
+        Confirm that:\n
+           - Factory data reset is disabled.\n
+           - Pressing factory data reset is not possible and triggers a support message.\n\n
+        2. Go to OEM unlocking settings, if this device has this Settings option. It is often located under \"Developer options\".\n
+        Confirm that:\n
+           - Oem Unlocking is disabled.\n
+           - Enabling Oem unlocking is not possible and triggers a support message.\n\n
+        Return back to this page.
+    </string>
     <string name="device_owner_user_restriction_set">Set restriction</string>
     <string name="device_owner_settings_go">Go</string>
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java
index 724f03d..024854c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java
@@ -68,6 +68,7 @@
     private static final String DISALLOW_USB_FILE_TRANSFER_ID = "DISALLOW_USB_FILE_TRANSFER";
     private static final String SET_USER_ICON_TEST_ID = "SET_USER_ICON";
     private static final String DISALLOW_DATA_ROAMING_ID = "DISALLOW_DATA_ROAMING";
+    private static final String DISALLOW_FACTORY_RESET_ID = "DISALLOW_FACTORY_RESET";
     private static final String POLICY_TRANSPARENCY_TEST_ID = "POLICY_TRANSPARENCY";
     private static final String REMOVE_DEVICE_OWNER_TEST_ID = "REMOVE_DEVICE_OWNER";
 
@@ -201,6 +202,16 @@
                                     new Intent(Settings.ACTION_DATA_ROAMING_SETTINGS))}));
         }
 
+        // DISALLOW_FACTORY_RESET
+        adapter.add(createInteractiveTestItem(this, DISALLOW_FACTORY_RESET_ID,
+                R.string.device_owner_disallow_factory_reset,
+                R.string.device_owner_disallow_factory_reset_info,
+                new ButtonInfo[] {
+                        new ButtonInfo(
+                                R.string.device_owner_user_restriction_set,
+                                createSetUserRestrictionIntent(
+                                        UserManager.DISALLOW_FACTORY_RESET))}));
+
         // DISALLOW_CONFIG_BLUETOOTH
         if (packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
             adapter.add(createInteractiveTestItem(this, DISALLOW_CONFIG_BT_ID,
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DeviceInfoCollector.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DeviceInfoCollector.java
index dd91d11..ff4e4bf 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DeviceInfoCollector.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/DeviceInfoCollector.java
@@ -18,6 +18,7 @@
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.compatibility.common.tradefed.testtype.CompatibilityTest;
+import com.android.compatibility.common.tradefed.util.CollectorUtil;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.config.Option;
 import com.android.tradefed.device.DeviceNotAvailableException;
@@ -105,22 +106,9 @@
                 return;
             }
             String resultPath = resultDir.getAbsolutePath();
-            pull(device, mSrcDir, resultPath);
+            CollectorUtil.pullFromDevice(device, mSrcDir, resultPath);
         } catch (FileNotFoundException fnfe) {
             fnfe.printStackTrace();
         }
     }
-
-    private void pull(ITestDevice device, String src, String dest) {
-        String command = String.format("adb -s %s pull %s %s", device.getSerialNumber(), src, dest);
-        try {
-            Process p = Runtime.getRuntime().exec(new String[] {"/bin/bash", "-c", command});
-            if (p.waitFor() != 0) {
-                CLog.e("Failed to run %s", command);
-            }
-        } catch (Exception e) {
-            CLog.e("Caught exception during pull.");
-            CLog.e(e);
-        }
-    }
 }
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/ReportLogCollector.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/ReportLogCollector.java
index a1c8a4a..576b15d 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/ReportLogCollector.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/targetprep/ReportLogCollector.java
@@ -17,6 +17,7 @@
 package com.android.compatibility.common.tradefed.targetprep;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.compatibility.common.tradefed.util.CollectorUtil;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.config.Option;
 import com.android.tradefed.device.DeviceNotAvailableException;
@@ -27,18 +28,8 @@
 import com.android.tradefed.targetprep.TargetSetupError;
 import com.android.tradefed.util.FileUtil;
 
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
 import java.io.File;
 import java.io.FileNotFoundException;
-import java.io.FileReader;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 /**
  * An {@link ITargetCleaner} that prepares and pulls report logs.
@@ -103,93 +94,12 @@
                 CLog.e("%s is not a directory", hostReportDir.getAbsolutePath());
                 return;
             }
-            pull(device, mSrcDir, hostReportDir, resultDir);
-            reformatRepeatedStreams(resultDir);
+            String resultPath = resultDir.getAbsolutePath();
+            CollectorUtil.pullFromDevice(device, mSrcDir, resultPath);
+            CollectorUtil.pullFromHost(hostReportDir, resultDir);
+            CollectorUtil.reformatRepeatedStreams(resultDir);
         } catch (Exception exception) {
             exception.printStackTrace();
         }
     }
-
-    private void pull(ITestDevice device, String deviceSrc, File hostDir, File destDir) {
-        String hostSrc = hostDir.getAbsolutePath();
-        String dest = destDir.getAbsolutePath();
-        String deviceSideCommand = String.format("adb -s %s pull %s %s", device.getSerialNumber(),
-                deviceSrc, dest);
-        try {
-            if (device.doesFileExist(deviceSrc)) {
-                Process deviceProcess = Runtime.getRuntime().exec(new String[]{"/bin/bash", "-c",
-                        deviceSideCommand});
-                if (deviceProcess.waitFor() != 0) {
-                    CLog.e("Failed to run %s", deviceSideCommand);
-                }
-            }
-            FileUtil.recursiveCopy(hostDir, destDir);
-            FileUtil.recursiveDelete(hostDir);
-        } catch (Exception e) {
-            CLog.e("Caught exception during pull.");
-            CLog.e(e);
-        }
-    }
-
-    private void reformatRepeatedStreams(File resultDir) throws IOException, FileNotFoundException {
-        File[] reportLogs = resultDir.listFiles();
-        // Sometimes report logs are in a sub-directory.
-        if (reportLogs.length == 1 && reportLogs[0].isDirectory()) {
-            reportLogs = reportLogs[0].listFiles();
-        }
-        for (File reportLog : reportLogs) {
-            try (BufferedReader metricsReader = new BufferedReader(new FileReader(reportLog))) {
-                // Get metrics as string.
-                StringBuilder metricBuilder = new StringBuilder();
-                String line;
-                while ((line = metricsReader.readLine()) != null) {
-                    metricBuilder.append(line);
-                }
-                String metrics = metricBuilder.toString();
-                // Create map of stream names and metrics.
-                HashMap<String, List<String>> metricsMap = new HashMap<>();
-                String pattern = "\\\"([a-z0-9_]*)\\\":(\\{[^{}]*\\})";
-                Pattern p = Pattern.compile(pattern);
-                Matcher m = p.matcher(metrics);
-                while (m.find()) {
-                    String key = m.group(1);
-                    String value = m.group(2);
-                    if (!metricsMap.containsKey(key)) {
-                        metricsMap.put(key, new ArrayList<String>());
-                    }
-                    metricsMap.get(key).add(value);
-                }
-                // Rewrite metrics as arrays.
-                StringBuilder newMetricsBuilder = new StringBuilder();
-                newMetricsBuilder.append("{");
-                boolean firstLine = true;
-                for (String key: metricsMap.keySet()) {
-                    if (!firstLine) {
-                        newMetricsBuilder.append(",");
-                    } else {
-                        firstLine = false;
-                    }
-                    newMetricsBuilder.append("\"").append(key).append("\":[");
-                    boolean firstValue = true;
-                    for (String stream : metricsMap.get(key)) {
-                        if (!firstValue) {
-                            newMetricsBuilder.append(",");
-                        }
-                        else {
-                            firstValue = false;
-                        }
-                        newMetricsBuilder.append(stream);
-                    }
-                    newMetricsBuilder.append("]");
-                }
-                newMetricsBuilder.append("}");
-                reportLog.createNewFile();
-                try (BufferedWriter metricsWriter = new BufferedWriter(new
-                        FileWriter(reportLog))) {
-                    String newMetrics = newMetricsBuilder.toString();
-                    metricsWriter.write(newMetrics, 0, newMetrics.length());
-                }
-            }
-        }
-    }
 }
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
index 0154909..d53fa42 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
@@ -570,12 +570,18 @@
             }
             // Append each test that failed or was not executed to the filters
             for (IModuleResult module : result.getModules()) {
-                for (ICaseResult testResultList : module.getResults()) {
-                    for (ITestResult testResult : testResultList.getResults(TestStatus.PASS)) {
-                        // Create the filter for the test to be run.
-                        TestFilter filter = new TestFilter(
-                                module.getAbi(), module.getName(), testResult.getFullName());
-                        mExcludeFilters.add(filter.toString());
+                if (module.isPassed()) {
+                    // Whole module passed, exclude entire module
+                    TestFilter filter = new TestFilter(module.getAbi(), module.getName(), null);
+                    mExcludeFilters.add(filter.toString());
+                } else {
+                    for (ICaseResult testResultList : module.getResults()) {
+                        for (ITestResult testResult : testResultList.getResults(TestStatus.PASS)) {
+                            // Test passed, exclude it for retry
+                            TestFilter filter = new TestFilter(
+                                    module.getAbi(), module.getName(), testResult.getFullName());
+                            mExcludeFilters.add(filter.toString());
+                        }
                     }
                 }
             }
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/util/CollectorUtil.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/util/CollectorUtil.java
new file mode 100644
index 0000000..1321f22
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/util/CollectorUtil.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.tradefed.util;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.util.FileUtil;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Util class for {@link ReportLogCollector} and {@link DeviceInfoCollector}.
+ */
+public class CollectorUtil {
+
+    private CollectorUtil() {
+    }
+
+    private static final String ADB_LS_PATTERN = "([^\\s]+)\\s*";
+    private static final String TEST_METRICS_PATTERN = "\\\"([a-z0-9_]*)\\\":(\\{[^{}]*\\})";
+
+    /**
+     * Copy files from device to host.
+     * @param device The device reference.
+     * @param src The source directory on the device.
+     * @param dest The destination directory.
+     */
+    public static void pullFromDevice(ITestDevice device, String src, String dest) {
+        try {
+            if (device.doesFileExist(src)) {
+                String listCommand = String.format("ls %s", src);
+                String fileList = device.executeShellCommand(listCommand);
+                Pattern p = Pattern.compile(ADB_LS_PATTERN);
+                Matcher m = p.matcher(fileList);
+                while (m.find()) {
+                    String fileName = m.group(1);
+                    String srcPath = String.format("%s%s", src, fileName);
+                    File destFile = new File(String.format("%s/%s", dest, fileName));
+                    device.pullFile(srcPath, destFile);
+                }
+            }
+        } catch (DeviceNotAvailableException e) {
+            CLog.e("Caught exception during pull.");
+            CLog.e(e);
+        }
+    }
+
+    /**
+     * Copy files from host and delete from source.
+     * @param src The source directory.
+     * @param dest The destination directory.
+     */
+    public static void pullFromHost(File src, File dest) {
+        try {
+            FileUtil.recursiveCopy(src, dest);
+            FileUtil.recursiveDelete(src);
+        } catch (IOException e) {
+            CLog.e("Caught exception during pull.");
+            CLog.e(e);
+        }
+    }
+
+    /**
+     * Reformat test metrics jsons to convert multiple json objects with identical stream names into
+     * arrays of objects (b/28790467).
+     *
+     * @param resultDir The directory containing test metrics.
+     */
+    public static void reformatRepeatedStreams(File resultDir) {
+        try {
+            File[] reportLogs = resultDir.listFiles();
+            for (File reportLog : reportLogs) {
+                writeFile(reportLog, reformatJsonString(readFile(reportLog)));
+            }
+        } catch (IOException e) {
+            CLog.e("Caught exception during reformatting.");
+            CLog.e(e);
+        }
+    }
+
+    /**
+     * Helper function to read a file.
+     *
+     * @throws IOException
+     */
+    private static String readFile(File file) throws IOException {
+        StringBuilder stringBuilder = new StringBuilder();
+        try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                stringBuilder.append(line);
+            }
+        }
+        return stringBuilder.toString();
+    }
+
+    /**
+     * Helper function to write to a file.
+     *
+     * @param file {@link File} to write to.
+     * @param jsonString String to be written.
+     * @throws IOException
+     */
+    private static void writeFile(File file, String jsonString) throws IOException {
+        file.createNewFile();
+        try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
+            writer.write(jsonString, 0, jsonString.length());
+        }
+    }
+
+    /**
+     * Helper function to reformat JSON string.
+     *
+     * @param jsonString
+     * @return
+     */
+    public static String reformatJsonString(String jsonString) {
+        StringBuilder newJsonBuilder = new StringBuilder();
+        // Create map of stream names and json objects.
+        HashMap<String, List<String>> jsonMap = new HashMap<>();
+        Pattern p = Pattern.compile(TEST_METRICS_PATTERN);
+        Matcher m = p.matcher(jsonString);
+        while (m.find()) {
+            String key = m.group(1);
+            String value = m.group(2);
+            if (!jsonMap.containsKey(key)) {
+                jsonMap.put(key, new ArrayList<String>());
+            }
+            jsonMap.get(key).add(value);
+        }
+        // Rewrite json string as arrays.
+        newJsonBuilder.append("{");
+        boolean firstLine = true;
+        for (String key : jsonMap.keySet()) {
+            if (!firstLine) {
+                newJsonBuilder.append(",");
+            } else {
+                firstLine = false;
+            }
+            newJsonBuilder.append("\"").append(key).append("\":[");
+            boolean firstValue = true;
+            for (String stream : jsonMap.get(key)) {
+                if (!firstValue) {
+                    newJsonBuilder.append(",");
+                } else {
+                    firstValue = false;
+                }
+                newJsonBuilder.append(stream);
+            }
+            newJsonBuilder.append("]");
+        }
+        newJsonBuilder.append("}");
+        return newJsonBuilder.toString();
+    }
+}
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
index b710128..dfe67c1 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
@@ -25,6 +25,7 @@
 import com.android.compatibility.common.tradefed.testtype.ModuleDefTest;
 import com.android.compatibility.common.tradefed.testtype.ModuleRepoTest;
 import com.android.compatibility.common.tradefed.util.OptionHelperTest;
+import com.android.compatibility.common.tradefed.util.CollectorUtilTest;
 
 import junit.framework.Test;
 import junit.framework.TestSuite;
@@ -45,6 +46,7 @@
         addTestSuite(ResultReporterTest.class);
         addTestSuite(CompatibilityTestTest.class);
         addTestSuite(OptionHelperTest.class);
+        addTestSuite(CollectorUtilTest.class);
         addTestSuite(ModuleDefTest.class);
         addTestSuite(ModuleRepoTest.class);
         addTestSuite(PropertyCheckTest.class);
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/util/CollectorUtilTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/util/CollectorUtilTest.java
new file mode 100644
index 0000000..f36a1f5
--- /dev/null
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/util/CollectorUtilTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.compatibility.common.tradefed.util;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for {@link CollectorUtil}
+ */
+public class CollectorUtilTest extends TestCase {
+
+    String UNFORMATTED_JSON = "{"
+            + "\"stream_name_1\":"
+            + "{\"id\":1,\"key1\":\"value1\"},"
+            + "\"stream_name_2\":"
+            + "{\"id\":1,\"key1\":\"value3\"},"
+            + "\"stream_name_1\":"
+            + "{\"id\":2,\"key1\":\"value2\"},"
+            + "}";
+
+    String REFORMATTED_JSON = "{"
+            + "\"stream_name_2\":"
+            + "["
+            + "{\"id\":1,\"key1\":\"value3\"}"
+            + "],"
+            + "\"stream_name_1\":"
+            + "["
+            + "{\"id\":1,\"key1\":\"value1\"},"
+            + "{\"id\":2,\"key1\":\"value2\"}"
+            + "]"
+            + "}";
+
+    public void testReformatJsonString() throws Exception {
+        String reformattedJson = CollectorUtil.reformatJsonString(UNFORMATTED_JSON);
+        assertEquals(reformattedJson, REFORMATTED_JSON);
+    }
+
+}
diff --git a/common/util/src/com/android/compatibility/common/util/IModuleResult.java b/common/util/src/com/android/compatibility/common/util/IModuleResult.java
index 2c49559..41a54de 100644
--- a/common/util/src/com/android/compatibility/common/util/IModuleResult.java
+++ b/common/util/src/com/android/compatibility/common/util/IModuleResult.java
@@ -36,6 +36,8 @@
 
     void setDone(boolean done);
 
+    boolean isPassed();
+
     /**
      * Gets a {@link ICaseResult} for the given testcase, creating it if it doesn't exist.
      *
diff --git a/common/util/src/com/android/compatibility/common/util/ModuleResult.java b/common/util/src/com/android/compatibility/common/util/ModuleResult.java
index 9fce4d8..211a69e 100644
--- a/common/util/src/com/android/compatibility/common/util/ModuleResult.java
+++ b/common/util/src/com/android/compatibility/common/util/ModuleResult.java
@@ -60,6 +60,15 @@
      * {@inheritDoc}
      */
     @Override
+    public boolean isPassed() {
+        return mDone &&
+                (countResults(TestStatus.FAIL) + countResults(TestStatus.NOT_EXECUTED) == 0);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
     public String getId() {
         return mId;
     }
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v1-only-with-rsa-1024-cert-not-der.apk b/hostsidetests/appsecurity/res/pkgsigverify/v1-only-with-rsa-1024-cert-not-der.apk
new file mode 100644
index 0000000..28c8e0c
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v1-only-with-rsa-1024-cert-not-der.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v1-only-with-rsa-1024.apk b/hostsidetests/appsecurity/res/pkgsigverify/v1-only-with-rsa-1024.apk
new file mode 100644
index 0000000..c1dff8a
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v1-only-with-rsa-1024.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v2-only-with-rsa-pkcs1-sha256-1024-cert-not-der.apk b/hostsidetests/appsecurity/res/pkgsigverify/v2-only-with-rsa-pkcs1-sha256-1024-cert-not-der.apk
new file mode 100644
index 0000000..ada1b00
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v2-only-with-rsa-pkcs1-sha256-1024-cert-not-der.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java
index 6a47676..c0c7dc0 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java
@@ -355,6 +355,70 @@
         assertInstallSucceeds("v1-only-with-rsa-pkcs1-sha1-2048.apk");
     }
 
+    public void testV1SchemeSignatureCertNotReencoded() throws Exception {
+        // Regression test for b/30148997 and b/18228011. When PackageManager does not preserve the
+        // original encoded form of signing certificates, bad things happen, such as rejection of
+        // completely valid updates to apps. The issue in b/30148997 and b/18228011 was that
+        // PackageManager started re-encoding signing certs into DER. This normally produces exactly
+        // the original form because X.509 certificates are supposed to be DER-encoded. However, a
+        // small fraction of Android apps uses X.509 certificates which are not DER-encoded. For
+        // such apps, re-encoding into DER changes the serialized form of the certificate, creating
+        // a mismatch with the serialized form stored in the PackageManager database, leading to the
+        // rejection of updates for the app.
+        //
+        // The signing certs of the two APKs differ only in how the cert's signature is encoded.
+        // From Android's perspective, these two APKs are signed by different entities and thus
+        // cannot be used to update one another. If signature verification code re-encodes certs
+        // into DER, both certs will be exactly the same and Android will accept these APKs as
+        // updates of each other. This test is thus asserting that the two APKs are not accepted as
+        // updates of each other.
+        //
+        // * v1-only-with-rsa-1024.apk cert's signature is DER-encoded
+        // * v1-only-with-rsa-1024-cert-not-der.apk cert's signature is not DER-encoded. It is
+        //   BER-encoded, with length encoded as two bytes instead of just one.
+        //   v1-only-with-rsa-1024-cert-not-der.apk META-INF/CERT.RSA was obtained from
+        //   v1-only-with-rsa-1024.apk META-INF/CERT.RSA by manually modifying the ASN.1 structure.
+        assertInstallSucceeds("v1-only-with-rsa-1024.apk");
+        assertInstallFailsWithError(
+                "v1-only-with-rsa-1024-cert-not-der.apk", "signatures do not match");
+
+        uninstallPackage();
+        assertInstallSucceeds("v1-only-with-rsa-1024-cert-not-der.apk");
+        assertInstallFailsWithError("v1-only-with-rsa-1024.apk", "signatures do not match");
+    }
+
+    public void testV2SchemeSignatureCertNotReencoded() throws Exception {
+        // This test is here to catch something like b/30148997 and b/18228011 happening to the
+        // handling of APK Signature Scheme v2 signatures by PackageManager. When PackageManager
+        // does not preserve the original encoded form of signing certificates, bad things happen,
+        // such as rejection of completely valid updates to apps. The issue in b/30148997 and
+        // b/18228011 was that PackageManager started re-encoding signing certs into DER. This
+        // normally produces exactly the original form because X.509 certificates are supposed to be
+        // DER-encoded. However, a small fraction of Android apps uses X.509 certificates which are
+        // not DER-encoded. For such apps, re-encoding into DER changes the serialized form of the
+        // certificate, creating a mismatch with the serialized form stored in the PackageManager
+        // database, leading to the rejection of updates for the app.
+        //
+        // The signing certs of the two APKs differ only in how the cert's signature is encoded.
+        // From Android's perspective, these two APKs are signed by different entities and thus
+        // cannot be used to update one another. If signature verification code re-encodes certs
+        // into DER, both certs will be exactly the same and Android will accept these APKs as
+        // updates of each other. This test is thus asserting that the two APKs are not accepted as
+        // updates of each other.
+        //
+        // * v2-only-with-rsa-pkcs1-sha256-1024.apk cert's signature is DER-encoded
+        // * v2-only-with-rsa-pkcs1-sha256-1024-cert-not-der.apk cert's signature is not DER-encoded
+        //   It is BER-encoded, with length encoded as two bytes instead of just one.
+        assertInstallSucceeds("v2-only-with-rsa-pkcs1-sha256-1024.apk");
+        assertInstallFailsWithError(
+                "v2-only-with-rsa-pkcs1-sha256-1024-cert-not-der.apk", "signatures do not match");
+
+        uninstallPackage();
+        assertInstallSucceeds("v2-only-with-rsa-pkcs1-sha256-1024-cert-not-der.apk");
+        assertInstallFailsWithError(
+                "v2-only-with-rsa-pkcs1-sha256-1024.apk", "signatures do not match");
+    }
+
     private void assertInstallSucceeds(String apkFilenameInResources) throws Exception {
         String installResult = installPackageFromResource(apkFilenameInResources);
         if (installResult != null) {
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AlwaysOnVpnMultiStageTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AlwaysOnVpnMultiStageTest.java
new file mode 100644
index 0000000..f6604ee
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AlwaysOnVpnMultiStageTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.deviceandprofileowner;
+
+import android.content.pm.PackageManager;
+import android.system.ErrnoException;
+import android.system.OsConstants;
+
+import com.android.cts.deviceandprofileowner.vpn.VpnTestHelper;
+
+import static com.android.cts.deviceandprofileowner.vpn.VpnTestHelper.VPN_PACKAGE;
+import static com.android.cts.deviceandprofileowner.vpn.VpnTestHelper.TEST_ADDRESS;
+
+/**
+ * Contains methods to test always-on VPN invoked by DeviceAndProfileOwnerTest
+ */
+public class AlwaysOnVpnMultiStageTest extends BaseDeviceAdminTest {
+
+    public void testAlwaysOnSet() throws Exception {
+        // Setup always-on vpn
+        VpnTestHelper.setAndWaitForVpn(mContext, VPN_PACKAGE, /* usable */ true);
+        assertTrue(VpnTestHelper.isNetworkVpn(mContext));
+        VpnTestHelper.checkPing(TEST_ADDRESS);
+    }
+
+    public void testNetworkBlocked() throws Exception {
+        // After the vpn app being force-stop, expect that always-on package stays the same
+        assertEquals(VPN_PACKAGE, mDevicePolicyManager.getAlwaysOnVpnPackage(
+                ADMIN_RECEIVER_COMPONENT));
+        assertFalse(VpnTestHelper.isNetworkVpn(mContext));
+        // Expect the network is still locked down after the vpn app process is killed
+        try {
+            VpnTestHelper.tryPosixConnect(TEST_ADDRESS);
+            fail("sendIcmpMessage doesn't throw Exception during network lockdown");
+        } catch (ErrnoException e) {
+            // Os.connect returns ENETUNREACH errno after the vpn app process is killed
+            assertEquals(OsConstants.ENETUNREACH, e.errno);
+        }
+    }
+
+    public void testAlwaysOnVpnDisabled() throws Exception {
+        // After the vpn app being uninstalled, check that always-on vpn is null
+        assertNull(mDevicePolicyManager.getAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT));
+        assertFalse(VpnTestHelper.isNetworkVpn(mContext));
+    }
+
+    public void testSetNonExistingPackage() throws Exception {
+        assertNull(mDevicePolicyManager.getAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT));
+
+        // Verify it throws NameNotFoundException for non-existing package after uninstallation
+        try {
+            mDevicePolicyManager.setAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT, VPN_PACKAGE,
+                    true);
+            fail("setAlwaysOnVpnPackage should not accept non-vpn package");
+        } catch (PackageManager.NameNotFoundException e) {
+            // success
+        }
+
+        assertNull(mDevicePolicyManager.getAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT));
+    }
+
+    public void testCleanup() throws Exception {
+        mDevicePolicyManager.setAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT, null, false);
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AlwaysOnVpnTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AlwaysOnVpnTest.java
index 67dd941..34566a1 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AlwaysOnVpnTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AlwaysOnVpnTest.java
@@ -16,33 +16,12 @@
 
 package com.android.cts.deviceandprofileowner;
 
-import android.content.pm.PackageManager;
-import android.net.ConnectivityManager.NetworkCallback;
-import android.net.ConnectivityManager;
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.NetworkInfo;
-import android.net.NetworkRequest;
 import android.os.Bundle;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.StructPollfd;
 
-import java.io.ByteArrayOutputStream;
-import java.io.DataOutputStream;
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.Socket;
-import java.util.Arrays;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
+import com.android.cts.deviceandprofileowner.vpn.VpnTestHelper;
 
-import static android.system.OsConstants.AF_INET;
-import static android.system.OsConstants.IPPROTO_ICMP;
-import static android.system.OsConstants.POLLIN;
-import static android.system.OsConstants.SOCK_DGRAM;
+import static com.android.cts.deviceandprofileowner.vpn.VpnTestHelper.VPN_PACKAGE;
+import static com.android.cts.deviceandprofileowner.vpn.VpnTestHelper.TEST_ADDRESS;
 
 /**
  * Validates that a device owner or profile owner can set an always-on VPN without user action.
@@ -55,33 +34,18 @@
  * result of a misconfigured network.
  */
 public class AlwaysOnVpnTest extends BaseDeviceAdminTest {
-
-    private static final String VPN_PACKAGE = "com.android.cts.vpnfirewall";
-    private static final int NETWORK_TIMEOUT_MS = 5000;
-    private static final int NETWORK_SETTLE_GRACE_MS = 100;
-    private static final int SOCKET_TIMEOUT_MS = 5000;
-
     /** @see com.android.cts.vpnfirewall.ReflectorVpnService */
     public static final String RESTRICTION_ADDRESSES = "vpn.addresses";
     public static final String RESTRICTION_ROUTES = "vpn.routes";
     public static final String RESTRICTION_ALLOWED = "vpn.allowed";
     public static final String RESTRICTION_DISALLOWED = "vpn.disallowed";
 
-    private static final int ICMP_ECHO_REQUEST = 0x08;
-    private static final int ICMP_ECHO_REPLY = 0x00;
-
-    // IP address reserved for documentation by rfc5737
-    private static final String TEST_ADDRESS = "192.0.2.4";
-
-    private ConnectivityManager mConnectivityManager;
     private String mPackageName;
 
     @Override
     public void setUp() throws Exception {
         super.setUp();
         mPackageName = mContext.getPackageName();
-        mConnectivityManager =
-                (ConnectivityManager) mContext.getSystemService(mContext.CONNECTIVITY_SERVICE);
     }
 
     @Override
@@ -91,14 +55,12 @@
                 /* restrictions */ null);
         super.tearDown();
     }
-
     public void testAlwaysOnVpn() throws Exception {
         // test always-on is null by default
         assertNull(mDevicePolicyManager.getAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT));
 
-        final CountDownLatch vpnLatch = new CountDownLatch(1);
-        setAndWaitForVpn(VPN_PACKAGE, /* usable */ true);
-        checkPing(TEST_ADDRESS);
+        VpnTestHelper.setAndWaitForVpn(mContext, VPN_PACKAGE, /* usable */ true);
+        VpnTestHelper.checkPing(TEST_ADDRESS);
     }
 
     public void testAllowedApps() throws Exception {
@@ -106,8 +68,8 @@
         restrictions.putStringArray(RESTRICTION_ALLOWED, new String[] {mPackageName});
         mDevicePolicyManager.setApplicationRestrictions(ADMIN_RECEIVER_COMPONENT, VPN_PACKAGE,
                 restrictions);
-        setAndWaitForVpn(VPN_PACKAGE, /* usable */ true);
-        assertTrue(isNetworkVpn());
+        VpnTestHelper.setAndWaitForVpn(mContext, VPN_PACKAGE, /* usable */ true);
+        assertTrue(VpnTestHelper.isNetworkVpn(mContext));
     }
 
     public void testDisallowedApps() throws Exception {
@@ -115,110 +77,24 @@
         restrictions.putStringArray(RESTRICTION_DISALLOWED, new String[] {mPackageName});
         mDevicePolicyManager.setApplicationRestrictions(ADMIN_RECEIVER_COMPONENT, VPN_PACKAGE,
                 restrictions);
-        setAndWaitForVpn(VPN_PACKAGE, /* usable */ false);
-        assertFalse(isNetworkVpn());
+        VpnTestHelper.setAndWaitForVpn(mContext, VPN_PACKAGE, /* usable */ false);
+        assertFalse(VpnTestHelper.isNetworkVpn(mContext));
     }
 
-    private void setAndWaitForVpn(String packageName, boolean usable) {
-        final CountDownLatch vpnLatch = new CountDownLatch(1);
-        final NetworkRequest request = new NetworkRequest.Builder()
-                .addTransportType(NetworkCapabilities.TRANSPORT_VPN)
-                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
-                .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                .build();
-        final ConnectivityManager.NetworkCallback callback
-                = new ConnectivityManager.NetworkCallback() {
-            @Override
-            public void onAvailable(Network net) {
-                vpnLatch.countDown();
-            }
-        };
-        mConnectivityManager.registerNetworkCallback(request, callback);
+    public void testSetNonVpnAlwaysOn() throws Exception {
+        // test always-on is null by default
+        assertNull(mDevicePolicyManager.getAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT));
+
+        // Treat this CTS DPC as an non-vpn app, since it doesn't register
+        // android.net.VpnService intent filter in AndroidManifest.xml.
         try {
-            mDevicePolicyManager.setAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT, VPN_PACKAGE, true);
-            assertEquals(VPN_PACKAGE, mDevicePolicyManager.getAlwaysOnVpnPackage(
-                    ADMIN_RECEIVER_COMPONENT));
-            if (!vpnLatch.await(NETWORK_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
-                fail("Took too long waiting to establish a VPN-backed connection");
-            }
-            // Give the VPN a moment to start transmitting data.
-            Thread.sleep(NETWORK_SETTLE_GRACE_MS);
-        } catch (InterruptedException | PackageManager.NameNotFoundException e) {
-            fail("Failed to send ping: " + e);
-        } finally {
-            mConnectivityManager.unregisterNetworkCallback(callback);
+            mDevicePolicyManager.setAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT, mPackageName,
+                    true);
+            fail("setAlwaysOnVpnPackage should not accept non-vpn package");
+        } catch (UnsupportedOperationException e) {
+            // success
         }
-
-        // Do we have a network?
-        NetworkInfo vpnInfo = mConnectivityManager.getNetworkInfo(ConnectivityManager.TYPE_VPN);
-        assertTrue(vpnInfo != null);
-
-        // Is it usable?
-        assertEquals(usable, vpnInfo.isConnected());
-    }
-
-    private boolean isNetworkVpn() {
-        Network network = mConnectivityManager.getActiveNetwork();
-        NetworkCapabilities capabilities = mConnectivityManager.getNetworkCapabilities(network);
-        return capabilities != null && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN);
-    }
-
-    private static void checkPing(String host) throws ErrnoException, IOException {
-        FileDescriptor socket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
-
-        // Create an ICMP message
-        final int identifier = 0x7E57;
-        final String message = "test packet";
-        byte[] echo = createIcmpMessage(ICMP_ECHO_REQUEST, 0x00, identifier, 0, message.getBytes());
-
-        // Send the echo packet.
-        int port = new InetSocketAddress(0).getPort();
-        Os.connect(socket, InetAddress.getByName(host), port);
-        Os.write(socket, echo, 0, echo.length);
-
-        // Expect a reply.
-        StructPollfd pollfd = new StructPollfd();
-        pollfd.events = (short) POLLIN;
-        pollfd.fd = socket;
-        int ret = Os.poll(new StructPollfd[] { pollfd }, SOCKET_TIMEOUT_MS);
-        assertEquals("Expected reply after sending ping", 1, ret);
-
-        byte[] reply = new byte[echo.length];
-        int read = Os.read(socket, reply, 0, echo.length);
-        assertEquals(echo.length, read);
-
-        // Ignore control type differences since echo=8, reply=0.
-        assertEquals(echo[0], ICMP_ECHO_REQUEST);
-        assertEquals(reply[0], ICMP_ECHO_REPLY);
-        echo[0] = 0;
-        reply[0] = 0;
-
-        // Fix ICMP ID which kernel will have changed on the way out.
-        InetSocketAddress local = (InetSocketAddress) Os.getsockname(socket);
-        port = local.getPort();
-        echo[4] = (byte) ((port >> 8) & 0xFF);
-        echo[5] = (byte) (port & 0xFF);
-
-        // Ignore checksum differences since the types are not supposed to match.
-        echo[2] = echo[3] = 0;
-        reply[2] = reply[3] = 0;
-
-        assertTrue("Packet contents do not match."
-                + "\nEcho packet:  " + Arrays.toString(echo)
-                + "\nReply packet: " + Arrays.toString(reply), Arrays.equals(echo, reply));
-    }
-
-    private static byte[] createIcmpMessage(int type, int code, int extra1, int extra2,
-            byte[] data) throws IOException {
-        ByteArrayOutputStream output = new ByteArrayOutputStream();
-        DataOutputStream stream = new DataOutputStream(output);
-        stream.writeByte(type);
-        stream.writeByte(code);
-        stream.writeShort(/* checksum */ 0);
-        stream.writeShort((short) extra1);
-        stream.writeShort((short) extra2);
-        stream.write(data, 0, data.length);
-        return output.toByteArray();
+        assertNull(mDevicePolicyManager.getAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT));
     }
 }
 
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/BaseUserRestrictionsTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/BaseUserRestrictionsTest.java
index a7d8110..8b62604 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/BaseUserRestrictionsTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/BaseUserRestrictionsTest.java
@@ -85,7 +85,8 @@
 
     public static final String[] HIDDEN_AND_PROHIBITED = new String[] {
             "no_record_audio",
-            "no_wallpaper"
+            "no_wallpaper",
+            "no_oem_unlock"
     };
 
     protected void assertLayeredRestriction(String restriction, boolean expected) {
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/vpn/VpnTestHelper.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/vpn/VpnTestHelper.java
new file mode 100644
index 0000000..3072251
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/vpn/VpnTestHelper.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.deviceandprofileowner.vpn;
+
+import android.annotation.TargetApi;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.net.NetworkRequest;
+import android.os.Build.VERSION_CODES;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructPollfd;
+
+import com.android.cts.deviceandprofileowner.BaseDeviceAdminTest;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.IPPROTO_ICMP;
+import static android.system.OsConstants.POLLIN;
+import static android.system.OsConstants.SOCK_DGRAM;
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
+/**
+ * Helper class to test vpn status
+ */
+@TargetApi(VERSION_CODES.N)
+public class VpnTestHelper {
+    public static final String VPN_PACKAGE = "com.android.cts.vpnfirewall";
+
+    // IP address reserved for documentation by rfc5737
+    public static final String TEST_ADDRESS = "192.0.2.4";
+
+    private static final int SOCKET_TIMEOUT_MS = 5000;
+    private static final int ICMP_ECHO_REQUEST = 0x08;
+    private static final int ICMP_ECHO_REPLY = 0x00;
+    private static final int NETWORK_TIMEOUT_MS = 5000;
+    private static final int NETWORK_SETTLE_GRACE_MS = 100;
+    private static final ComponentName ADMIN_RECEIVER_COMPONENT =
+            BaseDeviceAdminTest.ADMIN_RECEIVER_COMPONENT;
+
+    public static void setAndWaitForVpn(Context context, String packageName, boolean usable) {
+        ConnectivityManager connectivityManager =
+                context.getSystemService(ConnectivityManager.class);
+        DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
+        final CountDownLatch vpnLatch = new CountDownLatch(1);
+        final NetworkRequest request = new NetworkRequest.Builder()
+                .addTransportType(NetworkCapabilities.TRANSPORT_VPN)
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                .build();
+        final ConnectivityManager.NetworkCallback callback
+                = new ConnectivityManager.NetworkCallback() {
+            @Override
+            public void onAvailable(Network net) {
+                vpnLatch.countDown();
+            }
+        };
+        connectivityManager.registerNetworkCallback(request, callback);
+        try {
+            dpm.setAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT, packageName, true);
+            assertEquals(packageName, dpm.getAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT));
+            if (!vpnLatch.await(NETWORK_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                fail("Took too long waiting to establish a VPN-backed connection");
+            }
+            // Give the VPN a moment to start transmitting data.
+            Thread.sleep(NETWORK_SETTLE_GRACE_MS);
+        } catch (InterruptedException | PackageManager.NameNotFoundException e) {
+            fail("Failed to send ping: " + e);
+        } finally {
+            connectivityManager.unregisterNetworkCallback(callback);
+        }
+
+        // Do we have a network?
+        NetworkInfo vpnInfo = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_VPN);
+        assertTrue(vpnInfo != null);
+
+        // Is it usable?
+        assertEquals(usable, vpnInfo.isConnected());
+    }
+
+
+    public static boolean isNetworkVpn(Context context) {
+        ConnectivityManager connectivityManager =
+                (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+        Network network = connectivityManager.getActiveNetwork();
+        NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network);
+        return capabilities != null && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN);
+    }
+
+    public static void checkPing(String host) throws ErrnoException, IOException {
+        FileDescriptor socket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
+
+        // Create an ICMP message
+        final int identifier = 0x7E57;
+        final String message = "test packet";
+        byte[] echo = createIcmpMessage(ICMP_ECHO_REQUEST, 0x00, identifier, 0, message.getBytes());
+
+        // Send the echo packet.
+        int port = new InetSocketAddress(0).getPort();
+        Os.connect(socket, InetAddress.getByName(host), port);
+        Os.write(socket, echo, 0, echo.length);
+
+        // Expect a reply.
+        StructPollfd pollfd = new StructPollfd();
+        pollfd.events = (short) POLLIN;
+        pollfd.fd = socket;
+        int ret = Os.poll(new StructPollfd[] { pollfd }, SOCKET_TIMEOUT_MS);
+        assertEquals("Expected reply after sending ping", 1, ret);
+
+        byte[] reply = new byte[echo.length];
+        int read = Os.read(socket, reply, 0, echo.length);
+        assertEquals(echo.length, read);
+
+        // Ignore control type differences since echo=8, reply=0.
+        assertEquals(echo[0], ICMP_ECHO_REQUEST);
+        assertEquals(reply[0], ICMP_ECHO_REPLY);
+        echo[0] = 0;
+        reply[0] = 0;
+
+        // Fix ICMP ID which kernel will have changed on the way out.
+        InetSocketAddress local = (InetSocketAddress) Os.getsockname(socket);
+        port = local.getPort();
+        echo[4] = (byte) ((port >> 8) & 0xFF);
+        echo[5] = (byte) (port & 0xFF);
+
+        // Ignore checksum differences since the types are not supposed to match.
+        echo[2] = echo[3] = 0;
+        reply[2] = reply[3] = 0;
+
+        assertTrue("Packet contents do not match."
+                + "\nEcho packet:  " + Arrays.toString(echo)
+                + "\nReply packet: " + Arrays.toString(reply), Arrays.equals(echo, reply));
+
+        // Close socket if the test pass. Otherwise, any error will kill the process.
+        Os.close(socket);
+    }
+
+    public static void tryPosixConnect(String host) throws ErrnoException, IOException {
+        FileDescriptor socket = null;
+        try {
+            socket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
+            int port = new InetSocketAddress(0).getPort();
+            Os.connect(socket, InetAddress.getByName(host), port);
+        } finally {
+            if (socket != null) {
+                Os.close(socket);
+            }
+        }
+    }
+
+    private static byte[] createIcmpMessage(int type, int code, int extra1, int extra2,
+            byte[] data) throws IOException {
+        ByteArrayOutputStream output = new ByteArrayOutputStream();
+        DataOutputStream stream = new DataOutputStream(output);
+        stream.writeByte(type);
+        stream.writeByte(code);
+        stream.writeShort(/* checksum */ 0);
+        stream.writeShort((short) extra1);
+        stream.writeShort((short) extra2);
+        stream.write(data, 0, data.length);
+        return output.toByteArray();
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
index 76e4c73..a5008e5 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
@@ -137,6 +137,17 @@
                 result);
     }
 
+    protected void forceStopPackageForUser(String packageName, int userId) throws Exception {
+        // TODO Move this logic to ITestDevice
+        executeShellCommand("am force-stop --user " + userId + " " + packageName);
+    }
+
+    private void executeShellCommand(final String command) throws Exception {
+        CLog.d("Starting command " + command);
+        String commandOutput = getDevice().executeShellCommand(command);
+        CLog.d("Output for command " + command + ": " + commandOutput);
+    }
+
     /** Initializes the user with the given id. This is required so that apps can run on it. */
     protected void startUser(int userId) throws Exception {
         getDevice().startUser(userId);
@@ -144,10 +155,7 @@
 
     protected void switchUser(int userId) throws Exception {
         // TODO Move this logic to ITestDevice
-        String command = "am switch-user " + userId;
-        CLog.d("Starting command " + command);
-        String commandOutput = getDevice().executeShellCommand(command);
-        CLog.d("Output for command " + command + ": " + commandOutput);
+        executeShellCommand("am switch-user " + userId);
     }
 
     protected int getMaxNumberOfUsersSupported() throws DeviceNotAvailableException {
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
index 70f5218..7b40ff1 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
@@ -175,6 +175,37 @@
         executeDeviceTestClass(".AlwaysOnVpnTest");
     }
 
+    public void testAlwaysOnVpnLockDown() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+
+        installAppAsUser(VPN_APP_APK, mUserId);
+        try {
+            executeDeviceTestMethod(".AlwaysOnVpnMultiStageTest", "testAlwaysOnSet");
+            forceStopPackageForUser(VPN_APP_PKG, mUserId);
+            executeDeviceTestMethod(".AlwaysOnVpnMultiStageTest", "testNetworkBlocked");
+        } finally {
+            executeDeviceTestMethod(".AlwaysOnVpnMultiStageTest", "testCleanup");
+        }
+    }
+
+    public void testAlwaysOnVpnPackageUninstalled() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+
+        installAppAsUser(VPN_APP_APK, mUserId);
+        try {
+            executeDeviceTestMethod(".AlwaysOnVpnMultiStageTest", "testAlwaysOnSet");
+            getDevice().uninstallPackage(VPN_APP_PKG);
+            executeDeviceTestMethod(".AlwaysOnVpnMultiStageTest", "testAlwaysOnVpnDisabled");
+            executeDeviceTestMethod(".AlwaysOnVpnMultiStageTest", "testSetNonExistingPackage");
+        } finally {
+            executeDeviceTestMethod(".AlwaysOnVpnMultiStageTest", "testCleanup");
+        }
+    }
+
     public void testPermissionPolicy() throws Exception {
         if (!mHasFeature) {
             return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
index 554f53b..b467aed 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
@@ -16,6 +16,10 @@
 
 package com.android.cts.devicepolicy;
 
+import com.android.tradefed.log.LogUtil.CLog;
+
+import java.lang.AssertionError;
+
 /**
  * Set of tests for managed profile owner use cases that also apply to device owners.
  * Tests that should be run identically in both cases are added in DeviceAndProfileOwnerTest.
@@ -69,13 +73,29 @@
         if (!mHasFeature) {
             return;
         }
-        executeDeviceTestMethod(".ScreenCaptureDisabledTest", "testSetScreenCaptureDisabled_true");
-        // start the ScreenCaptureDisabledActivity in the parent
-        installAppAsUser(DEVICE_ADMIN_APK, mParentUserId);
-        String command = "am start -W --user " + mParentUserId + " " + DEVICE_ADMIN_PKG + "/"
-                + DEVICE_ADMIN_PKG + ".ScreenCaptureDisabledActivity";
-        getDevice().executeShellCommand(command);
-        executeDeviceTestMethod(".ScreenCaptureDisabledTest", "testScreenCapturePossible");
+        runDumpsysWindow();
+        try {
+            executeDeviceTestMethod(".ScreenCaptureDisabledTest",
+                    "testSetScreenCaptureDisabled_true");
+            // start the ScreenCaptureDisabledActivity in the parent
+            installAppAsUser(DEVICE_ADMIN_APK, mParentUserId);
+            String command = "am start -W --user " + mParentUserId + " " + DEVICE_ADMIN_PKG + "/"
+                    + DEVICE_ADMIN_PKG + ".ScreenCaptureDisabledActivity";
+            getDevice().executeShellCommand(command);
+            executeDeviceTestMethod(".ScreenCaptureDisabledTest", "testScreenCapturePossible");
+        } catch (AssertionError e) {
+            runDumpsysWindow();
+            CLog.e("testScreenCaptureDisabled_allowedPrimaryUser failed", e);
+            fail("testScreenCaptureDisabled_allowedPrimaryUser failed");
+        }
+    }
+
+    // TODO: Remove this after investigation in b/28995242 is done
+    private void runDumpsysWindow() throws Exception {
+        String command = "dumpsys window displays";
+        CLog.d("Output for command " + command + ": " + getDevice().executeShellCommand(command));
+        command = "dumpsys window policy";
+        CLog.d("Output for command " + command + ": " + getDevice().executeShellCommand(command));
     }
 
     @Override
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java
index b89cf93..6669af5 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java
@@ -132,16 +132,29 @@
             setDozeMode(true);
             assertBackgroundNetworkAccess(false);
 
-            sendNotification(42);
-            assertBackgroundNetworkAccess(true);
-            // Make sure access is disabled after it expires
-            SystemClock.sleep(NETWORK_TIMEOUT_MS);
-            assertBackgroundNetworkAccess(false);
+            testNotification(4, NOTIFICATION_TYPE_CONTENT);
+            testNotification(8, NOTIFICATION_TYPE_DELETE);
+            testNotification(15, NOTIFICATION_TYPE_FULL_SCREEN);
+            testNotification(16, NOTIFICATION_TYPE_BUNDLE);
+            testNotification(23, NOTIFICATION_TYPE_ACTION);
+            testNotification(42, NOTIFICATION_TYPE_ACTION_BUNDLE);
+            testNotification(108, NOTIFICATION_TYPE_ACTION_REMOTE_INPUT);
         } finally {
             resetDeviceIdleSettings();
         }
     }
 
+    private void testNotification(int id, String type) throws Exception {
+        sendNotification(id, type);
+        assertBackgroundNetworkAccess(true);
+        if (type.equals(NOTIFICATION_TYPE_ACTION)) {
+            // Make sure access is disabled after it expires. Since this check considerably slows
+            // downs the CTS tests, do it just once.
+            SystemClock.sleep(NETWORK_TIMEOUT_MS);
+            assertBackgroundNetworkAccess(false);
+        }
+    }
+
     // Must override so it only tests foreground service - once an app goes to foreground, device
     // leaves Doze Mode.
     @Override
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index 439fbbe..d04aa0f 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -69,6 +69,18 @@
             "com.android.cts.net.hostside.app2.extra.RECEIVER_NAME";
     private static final String EXTRA_NOTIFICATION_ID =
             "com.android.cts.net.hostside.app2.extra.NOTIFICATION_ID";
+    private static final String EXTRA_NOTIFICATION_TYPE =
+            "com.android.cts.net.hostside.app2.extra.NOTIFICATION_TYPE";
+
+    protected static final String NOTIFICATION_TYPE_CONTENT = "CONTENT";
+    protected static final String NOTIFICATION_TYPE_DELETE = "DELETE";
+    protected static final String NOTIFICATION_TYPE_FULL_SCREEN = "FULL_SCREEN";
+    protected static final String NOTIFICATION_TYPE_BUNDLE = "BUNDLE";
+    protected static final String NOTIFICATION_TYPE_ACTION = "ACTION";
+    protected static final String NOTIFICATION_TYPE_ACTION_BUNDLE = "ACTION_BUNDLE";
+    protected static final String NOTIFICATION_TYPE_ACTION_REMOTE_INPUT = "ACTION_REMOTE_INPUT";
+
+
     private static final String NETWORK_STATUS_SEPARATOR = "\\|";
     private static final int SECOND_IN_MS = 1000;
     static final int NETWORK_TIMEOUT_MS = 15 * SECOND_IN_MS;
@@ -735,10 +747,12 @@
                 + "--receiver-foreground --receiver-registered-only");
     }
 
-    protected void sendNotification(int notificationId) {
+    protected void sendNotification(int notificationId, String notificationType) {
         final Intent intent = new Intent(ACTION_SEND_NOTIFICATION);
         intent.putExtra(EXTRA_NOTIFICATION_ID, notificationId);
-        Log.d(TAG, "Sending broadcast: " + intent);
+        intent.putExtra(EXTRA_NOTIFICATION_TYPE, notificationType);
+        Log.d(TAG, "Sending notification broadcast (id=" + notificationId + ", type="
+                + notificationType + ": " + intent);
         mContext.sendBroadcast(intent);
     }
 
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/MyNotificationListenerService.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/MyNotificationListenerService.java
index b9c3031..0893511 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/MyNotificationListenerService.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/MyNotificationListenerService.java
@@ -16,7 +16,10 @@
 package com.android.cts.net.hostside;
 
 import android.app.Notification;
+import android.app.PendingIntent;
 import android.app.PendingIntent.CanceledException;
+import android.app.RemoteInput;
+import android.os.Bundle;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.StatusBarNotification;
 import android.util.Log;
@@ -40,22 +43,75 @@
             Log.v(TAG, "ignoring notification from a different package");
             return;
         }
+        final PendingIntentSender sender = new PendingIntentSender();
         final Notification notification = sbn.getNotification();
-        if (notification.actions == null) {
-            Log.w(TAG, "ignoring notification without an action");
+        if (notification.contentIntent != null) {
+            sender.send("content", notification.contentIntent);
         }
-        for (Notification.Action action : notification.actions) {
-            Log.i(TAG, "Sending pending intent " + action.actionIntent);
-            try {
-                action.actionIntent.send();
-            } catch (CanceledException e) {
-                Log.w(TAG, "Pending Intent canceled");
+        if (notification.deleteIntent != null) {
+            sender.send("delete", notification.deleteIntent);
+        }
+        if (notification.fullScreenIntent != null) {
+            sender.send("full screen", notification.fullScreenIntent);
+        }
+        if (notification.actions != null) {
+            for (Notification.Action action : notification.actions) {
+                sender.send("action", action.actionIntent);
+                sender.send("action extras", action.getExtras());
+                final RemoteInput[] remoteInputs = action.getRemoteInputs();
+                if (remoteInputs != null && remoteInputs.length > 0) {
+                    for (RemoteInput remoteInput : remoteInputs) {
+                        sender.send("remote input extras", remoteInput.getExtras());
+                    }
+                }
             }
         }
+        sender.send("notification extras", notification.extras);
     }
 
     static String getId() {
         return String.format("%s/%s", MyNotificationListenerService.class.getPackage().getName(),
                 MyNotificationListenerService.class.getName());
     }
+
+    private static final class PendingIntentSender {
+        private PendingIntent mSentIntent = null;
+        private String mReason = null;
+
+        private void send(String reason, PendingIntent pendingIntent) {
+            if (pendingIntent == null) {
+                // Could happen on action that only has extras
+                Log.v(TAG, "Not sending null pending intent for " + reason);
+                return;
+            }
+            if (mSentIntent != null || mReason != null) {
+                // Sanity check: make sure test case set up just one pending intent in the
+                // notification, otherwise it could pass because another pending intent caused the
+                // whitelisting.
+                throw new IllegalStateException("Already sent a PendingIntent (" + mSentIntent
+                        + ") for reason '" + mReason + "' when requested another for '" + reason
+                        + "' (" + pendingIntent + ")");
+            }
+            Log.i(TAG, "Sending pending intent for " + reason + ":" + pendingIntent);
+            try {
+                pendingIntent.send();
+                mSentIntent = pendingIntent;
+                mReason = reason;
+            } catch (CanceledException e) {
+                Log.w(TAG, "Pending intent " + pendingIntent + " canceled");
+            }
+        }
+
+        private void send(String reason, Bundle extras) {
+            if (extras != null) {
+                for (String key : extras.keySet()) {
+                    Object value = extras.get(key);
+                    if (value instanceof PendingIntent) {
+                        send(reason + " with key '" + key + "'", (PendingIntent) value);
+                    }
+                }
+            }
+        }
+
+    }
 }
diff --git a/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/Common.java b/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/Common.java
index f02f651..8806e3b 100644
--- a/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/Common.java
+++ b/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/Common.java
@@ -43,6 +43,16 @@
             "com.android.cts.net.hostside.app2.extra.RECEIVER_NAME";
     static final String EXTRA_NOTIFICATION_ID =
             "com.android.cts.net.hostside.app2.extra.NOTIFICATION_ID";
+    static final String EXTRA_NOTIFICATION_TYPE =
+            "com.android.cts.net.hostside.app2.extra.NOTIFICATION_TYPE";
+
+    static final String NOTIFICATION_TYPE_CONTENT = "CONTENT";
+    static final String NOTIFICATION_TYPE_DELETE = "DELETE";
+    static final String NOTIFICATION_TYPE_FULL_SCREEN = "FULL_SCREEN";
+    static final String NOTIFICATION_TYPE_BUNDLE = "BUNDLE";
+    static final String NOTIFICATION_TYPE_ACTION = "ACTION";
+    static final String NOTIFICATION_TYPE_ACTION_BUNDLE = "ACTION_BUNDLE";
+    static final String NOTIFICATION_TYPE_ACTION_REMOTE_INPUT = "ACTION_REMOTE_INPUT";
 
     static int getUid(Context context) {
         final String packageName = context.getPackageName();
diff --git a/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/MyBroadcastReceiver.java b/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/MyBroadcastReceiver.java
index 60e5de1..3b82f42 100644
--- a/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/MyBroadcastReceiver.java
+++ b/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/MyBroadcastReceiver.java
@@ -25,8 +25,16 @@
 import static com.android.cts.net.hostside.app2.Common.ACTION_SEND_NOTIFICATION;
 import static com.android.cts.net.hostside.app2.Common.EXTRA_ACTION;
 import static com.android.cts.net.hostside.app2.Common.EXTRA_NOTIFICATION_ID;
+import static com.android.cts.net.hostside.app2.Common.EXTRA_NOTIFICATION_TYPE;
 import static com.android.cts.net.hostside.app2.Common.EXTRA_RECEIVER_NAME;
 import static com.android.cts.net.hostside.app2.Common.MANIFEST_RECEIVER;
+import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_ACTION;
+import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_ACTION_BUNDLE;
+import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_ACTION_REMOTE_INPUT;
+import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_BUNDLE;
+import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_CONTENT;
+import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_DELETE;
+import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_FULL_SCREEN;
 import static com.android.cts.net.hostside.app2.Common.TAG;
 import static com.android.cts.net.hostside.app2.Common.getUid;
 
@@ -34,6 +42,7 @@
 import android.app.Notification.Action;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.app.RemoteInput;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -230,21 +239,66 @@
      */
     private void sendNotification(Context context, Intent intent) {
         final int notificationId = intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1);
+        final String notificationType = intent.getStringExtra(EXTRA_NOTIFICATION_TYPE);
+        Log.d(TAG, "sendNotification: id=" + notificationId + ", type=" + notificationType
+                + ", intent=" + intent);
         final Intent serviceIntent = new Intent(context, MyService.class);
-        final PendingIntent pendingIntent = PendingIntent.getService(context, 0, serviceIntent, 0);
-        final Bundle badBundle = new Bundle();
-        badBundle.putCharSequence("parcelable", "I am not");
-        final Action action = new Action.Builder(
-                R.drawable.ic_notification, "ACTION", pendingIntent)
-                .addExtras(badBundle)
-                .build();
+        final PendingIntent pendingIntent = PendingIntent.getService(context, 0, serviceIntent,
+                notificationId);
+        final Bundle bundle = new Bundle();
+        bundle.putCharSequence("parcelable", "I am not");
 
-        final Notification notification = new Notification.Builder(context)
-                .setSmallIcon(R.drawable.ic_notification)
-                .setContentTitle("Light, Cameras...")
-                .setContentIntent(pendingIntent)
-                .addAction(action)
-                .build();
+        final Notification.Builder builder = new Notification.Builder(context)
+                .setSmallIcon(R.drawable.ic_notification);
+
+        Action action = null;
+        switch (notificationType) {
+            case NOTIFICATION_TYPE_CONTENT:
+                builder
+                    .setContentTitle("Light, Cameras...")
+                    .setContentIntent(pendingIntent);
+                break;
+            case NOTIFICATION_TYPE_DELETE:
+                builder.setDeleteIntent(pendingIntent);
+                break;
+            case NOTIFICATION_TYPE_FULL_SCREEN:
+                builder.setFullScreenIntent(pendingIntent, true);
+                break;
+            case NOTIFICATION_TYPE_BUNDLE:
+                bundle.putParcelable("Magnum P.I. (Pending Intent)", pendingIntent);
+                builder.setExtras(bundle);
+                break;
+            case NOTIFICATION_TYPE_ACTION:
+                action = new Action.Builder(
+                        R.drawable.ic_notification, "ACTION", pendingIntent)
+                        .build();
+                builder.addAction(action);
+                break;
+            case NOTIFICATION_TYPE_ACTION_BUNDLE:
+                bundle.putParcelable("Magnum A.P.I. (Action Pending Intent)", pendingIntent);
+                action = new Action.Builder(
+                        R.drawable.ic_notification, "ACTION WITH BUNDLE", null)
+                        .addExtras(bundle)
+                        .build();
+                builder.addAction(action);
+                break;
+            case NOTIFICATION_TYPE_ACTION_REMOTE_INPUT:
+                bundle.putParcelable("Magnum R.I. (Remote Input)", null);
+                final RemoteInput remoteInput = new RemoteInput.Builder("RI")
+                    .addExtras(bundle)
+                    .build();
+                action = new Action.Builder(
+                        R.drawable.ic_notification, "ACTION WITH REMOTE INPUT", pendingIntent)
+                        .addRemoteInput(remoteInput)
+                        .build();
+                builder.addAction(action);
+                break;
+            default:
+                Log.e(TAG, "Unknown notification type: " + notificationType);
+                return;
+        }
+
+        final Notification notification = builder.build();
         ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE))
             .notify(notificationId, notification);
     }
diff --git a/hostsidetests/os/src/android/os/cts/OsHostTests.java b/hostsidetests/os/src/android/os/cts/OsHostTests.java
index c7b3cc7..200ef40 100644
--- a/hostsidetests/os/src/android/os/cts/OsHostTests.java
+++ b/hostsidetests/os/src/android/os/cts/OsHostTests.java
@@ -125,8 +125,7 @@
                             Arrays.asList(hostgroup.split(" ")));
                     assertEquals(2, allHosts.size());
                     assertTrue("AllHosts Contains: " + allHosts, allHosts.contains(HOST_EXPLICIT));
-                    // Disable wildcard test until next API bump
-                    // assertTrue("AllHosts Contains: " + allHosts, allHosts.contains(HOST_WILDCARD));
+                    assertTrue("AllHosts Contains: " + allHosts, allHosts.contains(HOST_WILDCARD));
                     foundVerifierOutput = true;
                     break;
                 }
diff --git a/hostsidetests/services/activitymanager/app/AndroidManifest.xml b/hostsidetests/services/activitymanager/app/AndroidManifest.xml
index 5065ed6..00c6352 100755
--- a/hostsidetests/services/activitymanager/app/AndroidManifest.xml
+++ b/hostsidetests/services/activitymanager/app/AndroidManifest.xml
@@ -46,7 +46,7 @@
         />
         <activity android:name=".NoRelaunchActivity"
                 android:resizeableActivity="true"
-                android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+                android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|fontScale"
                 android:exported="true"
                 android:taskAffinity="nobody.but.NoRelaunchActivity"
         />
@@ -159,6 +159,15 @@
             android:exported="true"
             android:launchMode="singleInstance"
         />
+        <activity android:name=".TrampolineActivity"
+                  android:exported="true"
+                  android:theme="@android:style/Theme.NoDisplay"
+        />
+        <activity-alias android:enabled="true"
+                android:exported="true"
+                android:name=".EntryPointAliasActivity"
+                android:targetActivity=".TrampolineActivity" >
+        </activity-alias>
     </application>
 </manifest>
 
diff --git a/hostsidetests/services/activitymanager/app/src/android/server/app/TrampolineActivity.java b/hostsidetests/services/activitymanager/app/src/android/server/app/TrampolineActivity.java
new file mode 100644
index 0000000..4b80482
--- /dev/null
+++ b/hostsidetests/services/activitymanager/app/src/android/server/app/TrampolineActivity.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.app;
+
+import android.os.Bundle;
+
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
+import android.content.Intent;
+
+public class TrampolineActivity extends AbstractLifecycleLogActivity {
+
+    private static final String TAG = TrampolineActivity.class.getSimpleName();
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        // Add a delay here to expose more easily a failure case where the real target
+        // activity is visible before it's launched, because its task is being brought
+        // to foreground. We need to verify that 'am start' is unblocked correctly.
+        try {
+            Thread.sleep(2000);
+        } catch(InterruptedException e) {}
+        Intent intent = new Intent(this, SingleTaskActivity.class);
+        intent.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_NEW_TASK);
+
+        startActivity(intent);
+        finish();
+    }
+
+    @Override
+    protected String getTag() {
+        return TAG;
+    }
+}
diff --git a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerAmStartOptionsTests.java b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerAmStartOptionsTests.java
new file mode 100644
index 0000000..0083be4
--- /dev/null
+++ b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerAmStartOptionsTests.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.cts;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+
+public class ActivityManagerAmStartOptionsTests extends ActivityManagerTestBase {
+
+    private static final String TEST_ACTIVITY_NAME = "TestActivity";
+    private static final String ENTRYPOINT_ACTIVITY_NAME = "EntryPointAliasActivity";
+    private static final String SINGLE_TASK_ACTIVITY_NAME = "SingleTaskActivity";
+
+    public void testDashD() throws Exception {
+        final String[] waitForActivitiesVisible = new String[] {TEST_ACTIVITY_NAME};
+        AmStartLogcatVerifier verifier = new AmStartLogcatVerifier("android.server.app", TEST_ACTIVITY_NAME);
+
+        // Run at least 2 rounds to verify that -D works with an existing process.
+        // -D could fail in this case if the force stop of process is broken.
+        for (int i = 0; i < 2; i++) {
+            clearLogcat();
+            executeShellCommand(getAmStartCmd(TEST_ACTIVITY_NAME) + " -D");
+
+            // visibleOnly=false as the first window popping up will be the debugger window.
+            mAmWmState.computeState(mDevice, false, waitForActivitiesVisible);
+            verifier.verifyDashD();
+        }
+    }
+
+    public void testDashW_Direct() throws Exception {
+        testDashW(SINGLE_TASK_ACTIVITY_NAME, SINGLE_TASK_ACTIVITY_NAME);
+    }
+
+    public void testDashW_Indirect() throws Exception {
+        testDashW(ENTRYPOINT_ACTIVITY_NAME, SINGLE_TASK_ACTIVITY_NAME);
+    }
+
+    private void testDashW(final String entryActivity, final String actualActivity)
+            throws Exception {
+        AmStartLogcatVerifier verifier = new AmStartLogcatVerifier("android.server.app", actualActivity);
+
+        // Test cold start
+        startActivityAndVerifyResult(verifier, entryActivity, actualActivity, true);
+
+        // Test warm start
+        pressHomeButton();
+        startActivityAndVerifyResult(verifier, entryActivity, actualActivity, false);
+
+        // Test "hot" start (app already in front)
+        startActivityAndVerifyResult(verifier, entryActivity, actualActivity, false);
+    }
+
+    private static final Pattern sNotStartedWarningPattern = Pattern.compile(
+            "Warning: Activity not started(.*)");
+    private static final Pattern sStatusPattern = Pattern.compile(
+            "Status: (.*)");
+    private static final Pattern sActivityPattern = Pattern.compile(
+            "Activity: (.*)");
+    private static final String sStatusOk = "ok";
+
+    private void startActivityAndVerifyResult(
+            final AmStartLogcatVerifier verifier, final String entryActivity,
+            final String actualActivity, boolean shouldStart) throws Exception {
+        clearLogcat();
+
+        // Pass in different data only when cold starting. This is to make the intent
+        // different in subsequent warm/hot launches, so that the entrypoint alias
+        // activity is always started, but the actual activity is not started again
+        // because of the NEW_TASK and singleTask flags.
+        final String result = executeShellCommand(getAmStartCmd(entryActivity) + " -W"
+                + (shouldStart ? " -d about:blank" : ""));
+
+        // Verify shell command return value
+        verifyShellOutput(result, actualActivity, shouldStart);
+
+        // Verify adb logcat log
+        verifier.verifyDashW(shouldStart);
+    }
+
+    private void verifyShellOutput(
+            final String result, final String activity, boolean shouldStart) {
+        boolean warningFound = false;
+        String status = null;
+        String reportedActivity = null;
+        String componentActivityName = getActivityComponentName(activity);
+
+        for (String line : result.split("\\n")) {
+            Matcher matcher = sNotStartedWarningPattern.matcher(line);
+            if (matcher.matches()) {
+                warningFound = true;
+                continue;
+            }
+            matcher = sStatusPattern.matcher(line);
+            if (matcher.matches()) {
+                status = matcher.group(1);
+                continue;
+            }
+            matcher = sActivityPattern.matcher(line);
+            if (matcher.matches()) {
+                reportedActivity = matcher.group(1);
+                continue;
+            }
+        }
+
+        assertTrue("Status " + status + " is not ok", sStatusOk.equals(status));
+        assertTrue("Reported activity is " + reportedActivity + " not " + componentActivityName,
+                componentActivityName.equals(reportedActivity));
+
+        if (shouldStart && warningFound) {
+            fail("Should start new activity but brought something to front.");
+        } else if (!shouldStart && !warningFound){
+            fail("Should bring existing activity to front but started new activity.");
+        }
+    }
+
+    private static final Pattern sStartProcPattern =
+            Pattern.compile("(.+): Start proc (\\d+):(.*) for activity (.*)");
+    private static final Pattern sKillingPattern =
+            Pattern.compile("(.+): Killing (\\d+):(.*)");
+    private static final Pattern sWaitingForDebuggerPattern =
+            Pattern.compile("(.+): Application (.+) is waiting for the debugger (.*)");
+    private static final Pattern sDisplayTimePattern =
+            Pattern.compile("(.+): Displayed (.*): (\\+{0,1})([0-9]+)ms(.*)");
+
+    private class AmStartLogcatVerifier {
+        private String mPrevProcId;
+        private final String mPackageName;
+        private final String mActivityName;
+
+        AmStartLogcatVerifier(String packageName, String activityName) {
+            mPackageName = packageName;
+            mActivityName = activityName;
+        }
+
+        void verifyDashD() throws DeviceNotAvailableException {
+            boolean prevProcKilled = false;;
+            boolean waitingForDebugger = false;
+            String newProcId = null;
+            final String[] componentNames = new String[] {"ActivityManager", "ActivityThread"};
+
+            for (String line : getDeviceLogsForComponents(componentNames)) {
+                line = line.trim();
+
+                Matcher matcher = sStartProcPattern.matcher(line);
+                if (matcher.matches()) {
+                    final String activity = matcher.group(4);
+                    if (activity.contains(mActivityName)) {
+                        newProcId = matcher.group(2);
+                    }
+                    continue;
+                }
+
+                matcher = sKillingPattern.matcher(line);
+                if (matcher.matches()) {
+                    final String procId = matcher.group(2);
+                    if (procId.equals(mPrevProcId)) {
+                        prevProcKilled = true;
+                    }
+                    continue;
+                }
+
+                matcher = sWaitingForDebuggerPattern.matcher(line);
+                if (matcher.matches()) {
+                    final String packageName = matcher.group(2);
+                    if (packageName.equals(mPackageName)) {
+                        waitingForDebugger = true;
+                    }
+                    continue;
+                }
+            }
+
+            assertTrue("Didn't kill exisiting proc " + mPrevProcId + ".",
+                    mPrevProcId == null || prevProcKilled);
+            assertTrue("Didn't start new proc.", newProcId != null);
+            assertTrue("Didn't wait for debugger.", waitingForDebugger);
+
+            mPrevProcId = newProcId;
+        }
+
+        void verifyDashW(boolean shouldStart) throws DeviceNotAvailableException {
+            int displayCount = 0;
+            String activityName = null;
+
+            for (String line : getDeviceLogsForComponent("ActivityManager")) {
+                line = line.trim();
+
+                Matcher matcher = sDisplayTimePattern.matcher(line);
+                if (matcher.matches()) {
+                    activityName = matcher.group(2);
+                    // Ignore activitiy displays from other packages, we don't
+                    // want some random activity starts to ruin our test.
+                    if (!activityName.startsWith("android.server.app")) {
+                        continue;
+                    }
+                    if (!shouldStart) {
+                        fail("Shouldn't display anything but displayed " + activityName);
+                    }
+                    displayCount++;
+                }
+            }
+            final String expectedActivityName = getActivityComponentName(mActivityName);
+            if (shouldStart) {
+                if (displayCount != 1) {
+                    fail("Should display exactly one activity but displayed " + displayCount);
+                } else if (!expectedActivityName.equals(activityName)) {
+                    fail("Should display " + expectedActivityName +
+                            " but displayed " + activityName);
+                }
+            }
+        }
+    }
+}
diff --git a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerConfigChangeTests.java b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerConfigChangeTests.java
new file mode 100644
index 0000000..e5a121d
--- /dev/null
+++ b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerConfigChangeTests.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.cts;
+
+public class ActivityManagerConfigChangeTests extends ActivityManagerTestBase {
+
+    private static final String TEST_ACTIVITY_NAME = "TestActivity";
+    private static final String NO_RELAUNCH_ACTIVITY_NAME = "NoRelaunchActivity";
+
+    public void testRotation90Relaunch() throws Exception{
+        // Should relaunch on every rotation and receive no onConfigurationChanged()
+        testRotation(TEST_ACTIVITY_NAME, 1, 1, 0);
+    }
+
+    public void testRotation90NoRelaunch() throws Exception {
+        // Should receive onConfigurationChanged() on every rotation and no relaunch
+        testRotation(NO_RELAUNCH_ACTIVITY_NAME, 1, 0, 1);
+    }
+
+    public void testRotation180Relaunch() throws Exception {
+        // Should receive nothing
+        testRotation(TEST_ACTIVITY_NAME, 2, 0, 0);
+    }
+
+    public void testRotation180NoRelaunch() throws Exception {
+        // Should receive nothing
+        testRotation(NO_RELAUNCH_ACTIVITY_NAME, 2, 0, 0);
+    }
+
+    public void testChangeFontScaleRelaunch() throws Exception {
+        // Should relaunch and receive no onConfigurationChanged()
+        testChangeFontScale(TEST_ACTIVITY_NAME, true);
+    }
+
+    public void testChangeFontScaleNoRelaunch() throws Exception {
+        // Should receive onConfigurationChanged() and no relaunch
+        testChangeFontScale(NO_RELAUNCH_ACTIVITY_NAME, false);
+    }
+
+    private void testRotation(
+            String activityName, int rotationStep, int numRelaunch, int numConfigChange)
+                    throws Exception {
+        executeShellCommand(getAmStartCmd(activityName));
+
+        final String[] waitForActivitiesVisible = new String[] {activityName};
+        mAmWmState.computeState(mDevice, waitForActivitiesVisible);
+        mAmWmState.assertContainsStack(
+                "Must contain fullscreen stack.", FULLSCREEN_WORKSPACE_STACK_ID);
+
+        setDeviceRotation(4 - rotationStep);
+        mAmWmState.computeState(mDevice, waitForActivitiesVisible);
+
+        for (int rotation = 0; rotation < 4; rotation += rotationStep) {
+            clearLogcat();
+            setDeviceRotation(rotation);
+            mAmWmState.computeState(mDevice, waitForActivitiesVisible);
+            assertRelaunchOrConfigChanged(activityName, numRelaunch, numConfigChange);
+        }
+    }
+
+    private void testChangeFontScale(
+            String activityName, boolean relaunch) throws Exception {
+        executeShellCommand(getAmStartCmd(activityName));
+        final String[] waitForActivitiesVisible = new String[] {activityName};
+        mAmWmState.computeState(mDevice, waitForActivitiesVisible);
+        mAmWmState.assertContainsStack(
+                "Must contain fullscreen stack.", FULLSCREEN_WORKSPACE_STACK_ID);
+
+        setFontScale(1.0f);
+        mAmWmState.computeState(mDevice, waitForActivitiesVisible);
+
+        for (float fontScale = 0.85f; fontScale <= 1.3f; fontScale += 0.15f) {
+            clearLogcat();
+            setFontScale(fontScale);
+            mAmWmState.computeState(mDevice, waitForActivitiesVisible);
+            assertRelaunchOrConfigChanged(activityName, relaunch ? 1 : 0, relaunch ? 0 : 1);
+        }
+    }
+}
diff --git a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerTestBase.java b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerTestBase.java
index 3a21fda..7ef163e 100644
--- a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerTestBase.java
+++ b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerTestBase.java
@@ -74,6 +74,8 @@
 
     private static final String AM_MOVE_TASK = "am stack movetask ";
 
+    private static final String INPUT_KEYEVENT_HOME = "input keyevent 3";
+
     /** A reference to the device under test. */
     protected ITestDevice mDevice;
 
@@ -99,6 +101,7 @@
 
     private int mInitialAccelerometerRotation;
     private int mUserRotation;
+    private float mFontScale;
 
     @Override
     protected void setUp() throws Exception {
@@ -114,6 +117,7 @@
         // Store rotation settings.
         mInitialAccelerometerRotation = getAccelerometerRotation();
         mUserRotation = getUserRotation();
+        mFontScale = getFontScale();
     }
 
     @Override
@@ -125,6 +129,7 @@
             // Restore rotation settings to the state they were before test.
             setAccelerometerRotation(mInitialAccelerometerRotation);
             setUserRotation(mUserRotation);
+            setFontScale(mFontScale);
             // Remove special stacks.
             executeShellCommand(AM_REMOVE_STACK + PINNED_STACK_ID);
             executeShellCommand(AM_REMOVE_STACK + DOCKED_STACK_ID);
@@ -179,6 +184,10 @@
                 + " 0 0 " + taskWidth + " " + taskHeight);
     }
 
+    protected void pressHomeButton() throws DeviceNotAvailableException {
+        executeShellCommand(INPUT_KEYEVENT_HOME);
+    }
+
     // Utility method for debugging, not used directly here, but useful, so kept around.
     protected void printStacksAndTasks() throws DeviceNotAvailableException {
         CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
@@ -319,6 +328,28 @@
         }
     }
 
+    protected void setFontScale(float fontScale) throws DeviceNotAvailableException {
+        if (fontScale == 0.0f) {
+            runCommandAndPrintOutput(
+                    "settings delete system font_scale");
+        } else {
+            runCommandAndPrintOutput(
+                    "settings put system font_scale " + fontScale);
+        }
+    }
+
+    protected float getFontScale() throws DeviceNotAvailableException {
+        try {
+            final String fontScale =
+                    runCommandAndPrintOutput("settings get system font_scale").trim();
+            return Float.parseFloat(fontScale);
+        } catch (NumberFormatException e) {
+            // If we don't have a valid font scale key, return 0.0f now so
+            // that we delete the key in tearDown().
+            return 0.0f;
+        }
+    }
+
     protected String runCommandAndPrintOutput(String command) throws DeviceNotAvailableException {
         final String output = executeShellCommand(command);
         log(output);
@@ -359,10 +390,37 @@
         }
     }
 
-    private String[] getDeviceLogsForActivity(String activityName)
+    protected void assertRelaunchOrConfigChanged(
+            String activityName, int numRelaunch, int numConfigChange)
             throws DeviceNotAvailableException {
-        return mDevice.executeAdbCommand("logcat", "-v", "brief", "-d", activityName + ":I", "*:S")
-                .split("\\n");
+        final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(activityName);
+
+        if (lifecycleCounts.mDestroyCount != numRelaunch) {
+            fail(activityName + " has been destroyed " + lifecycleCounts.mDestroyCount
+                    + " time(s), expecting " + numRelaunch);
+        } else if (lifecycleCounts.mCreateCount != numRelaunch) {
+            fail(activityName + " has been (re)created " + lifecycleCounts.mCreateCount
+                    + " time(s), expecting " + numRelaunch);
+        } else if (lifecycleCounts.mConfigurationChangedCount != numConfigChange) {
+            fail(activityName + " has received " + lifecycleCounts.mConfigurationChangedCount
+                    + " onConfigurationChanged() calls, expecting " + numConfigChange);
+        }
+    }
+
+    protected String[] getDeviceLogsForComponent(String componentName)
+            throws DeviceNotAvailableException {
+        return mDevice.executeAdbCommand(
+                "logcat", "-v", "brief", "-d", componentName + ":I", "*:S").split("\\n");
+    }
+
+    protected String[] getDeviceLogsForComponents(final String[] componentNames)
+            throws DeviceNotAvailableException {
+        String filters = "";
+        for (int i = 0; i < componentNames.length; i++) {
+            filters += componentNames[i] + ":I ";
+        }
+        return mDevice.executeAdbCommand(
+                "logcat", "-v", "brief", "-d", filters, "*:S").split("\\n");
     }
 
     private static final Pattern sCreatePattern = Pattern.compile("(.+): onCreate");
@@ -386,7 +444,7 @@
 
     protected ReportedSizes getLastReportedSizesForActivity(String activityName)
             throws DeviceNotAvailableException {
-        final String[] lines = getDeviceLogsForActivity(activityName);
+        final String[] lines = getDeviceLogsForComponent(activityName);
         for (int i = lines.length - 1; i >= 0; i--) {
             final String line = lines[i].trim();
             final Matcher matcher = sNewConfigPattern.matcher(line);
@@ -410,7 +468,7 @@
         int mDestroyCount;
 
         public ActivityLifecycleCounts(String activityName) throws DeviceNotAvailableException {
-            for (String line : getDeviceLogsForActivity(activityName)) {
+            for (String line : getDeviceLogsForComponent(activityName)) {
                 line = line.trim();
 
                 Matcher matcher = sCreatePattern.matcher(line);
diff --git a/hostsidetests/services/activitymanager/src/android/server/cts/WindowManagerState.java b/hostsidetests/services/activitymanager/src/android/server/cts/WindowManagerState.java
index 3f66c18..a057013 100644
--- a/hostsidetests/services/activitymanager/src/android/server/cts/WindowManagerState.java
+++ b/hostsidetests/services/activitymanager/src/android/server/cts/WindowManagerState.java
@@ -49,6 +49,8 @@
             "mCurrentFocus=Window\\{([0-9a-fA-F]+) u(\\d+) (\\S+)\\}");
     private static final Pattern sAppErrorFocusedWindowPattern = Pattern.compile(
             "mCurrentFocus=Window\\{([0-9a-fA-F]+) u(\\d+) Application Error\\: (\\S+)\\}");
+    private static final Pattern sWaitingForDebuggerFocusedWindowPattern = Pattern.compile(
+            "mCurrentFocus=Window\\{([0-9a-fA-F]+) u(\\d+) Waiting For Debugger\\: (\\S+)\\}");
 
     private static final Pattern sFocusedAppPattern =
             Pattern.compile("mFocusedApp=AppWindowToken\\{(.+) token=Token\\{(.+) "
@@ -58,7 +60,8 @@
 
     private static final Pattern[] sExtractStackExitPatterns = {
             sStackIdPattern, sWindowPattern, sStartingWindowPattern, sExitingWindowPattern,
-            sFocusedWindowPattern, sAppErrorFocusedWindowPattern, sFocusedAppPattern };
+            sFocusedWindowPattern, sAppErrorFocusedWindowPattern,
+            sWaitingForDebuggerFocusedWindowPattern, sFocusedAppPattern };
 
     // Windows in z-order with the top most at the front of the list.
     private List<String> mWindows = new ArrayList();
@@ -182,6 +185,15 @@
                 continue;
             }
 
+            matcher = sWaitingForDebuggerFocusedWindowPattern.matcher(line);
+            if (matcher.matches()) {
+                log(line);
+                final String focusedWindow = matcher.group(3);
+                log(focusedWindow);
+                mFocusedWindow = focusedWindow;
+                continue;
+            }
+
             matcher = sFocusedAppPattern.matcher(line);
             if (matcher.matches()) {
                 log(line);
diff --git a/tests/app/src/android/app/cts/ActivityManagerMemoryClassTest.java b/tests/app/src/android/app/cts/ActivityManagerMemoryClassTest.java
index cb331d1..2b84be6 100644
--- a/tests/app/src/android/app/cts/ActivityManagerMemoryClassTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerMemoryClassTest.java
@@ -57,8 +57,11 @@
             expectedMemorySizeForWatch.put(DisplayMetrics.DENSITY_MEDIUM, 32);
             expectedMemorySizeForWatch.put(DisplayMetrics.DENSITY_TV, 32);
             expectedMemorySizeForWatch.put(DisplayMetrics.DENSITY_HIGH, 36);
+            expectedMemorySizeForWatch.put(DisplayMetrics.DENSITY_260, 36);
             expectedMemorySizeForWatch.put(DisplayMetrics.DENSITY_280, 36);
+            expectedMemorySizeForWatch.put(DisplayMetrics.DENSITY_300, 36);
             expectedMemorySizeForWatch.put(DisplayMetrics.DENSITY_XHIGH, 48);
+            expectedMemorySizeForWatch.put(DisplayMetrics.DENSITY_340, 48);
             expectedMemorySizeForWatch.put(DisplayMetrics.DENSITY_360, 48);
             expectedMemorySizeForWatch.put(DisplayMetrics.DENSITY_400, 56);
             expectedMemorySizeForWatch.put(DisplayMetrics.DENSITY_420, 64);
@@ -72,8 +75,11 @@
             expectedMemorySizeForSmallNormalScreen.put(DisplayMetrics.DENSITY_MEDIUM, 32);
             expectedMemorySizeForSmallNormalScreen.put(DisplayMetrics.DENSITY_TV, 48);
             expectedMemorySizeForSmallNormalScreen.put(DisplayMetrics.DENSITY_HIGH, 48);
+            expectedMemorySizeForSmallNormalScreen.put(DisplayMetrics.DENSITY_260, 48);
             expectedMemorySizeForSmallNormalScreen.put(DisplayMetrics.DENSITY_280, 48);
+            expectedMemorySizeForSmallNormalScreen.put(DisplayMetrics.DENSITY_300, 48);
             expectedMemorySizeForSmallNormalScreen.put(DisplayMetrics.DENSITY_XHIGH, 80);
+            expectedMemorySizeForSmallNormalScreen.put(DisplayMetrics.DENSITY_340, 80);
             expectedMemorySizeForSmallNormalScreen.put(DisplayMetrics.DENSITY_360, 80);
             expectedMemorySizeForSmallNormalScreen.put(DisplayMetrics.DENSITY_400, 96);
             expectedMemorySizeForSmallNormalScreen.put(DisplayMetrics.DENSITY_420, 112);
@@ -87,8 +93,11 @@
             expectedMemorySizeForLargeScreen.put(DisplayMetrics.DENSITY_MEDIUM, 64);
             expectedMemorySizeForLargeScreen.put(DisplayMetrics.DENSITY_TV, 80);
             expectedMemorySizeForLargeScreen.put(DisplayMetrics.DENSITY_HIGH, 80);
+            expectedMemorySizeForLargeScreen.put(DisplayMetrics.DENSITY_260, 96);
             expectedMemorySizeForLargeScreen.put(DisplayMetrics.DENSITY_280, 96);
+            expectedMemorySizeForLargeScreen.put(DisplayMetrics.DENSITY_300, 96);
             expectedMemorySizeForLargeScreen.put(DisplayMetrics.DENSITY_XHIGH, 128);
+            expectedMemorySizeForLargeScreen.put(DisplayMetrics.DENSITY_340, 160);
             expectedMemorySizeForLargeScreen.put(DisplayMetrics.DENSITY_360, 160);
             expectedMemorySizeForLargeScreen.put(DisplayMetrics.DENSITY_400, 192);
             expectedMemorySizeForLargeScreen.put(DisplayMetrics.DENSITY_420, 228);
@@ -102,8 +111,11 @@
             expectedMemorySizeForXLargeScreen.put(DisplayMetrics.DENSITY_MEDIUM, 80);
             expectedMemorySizeForXLargeScreen.put(DisplayMetrics.DENSITY_TV, 96);
             expectedMemorySizeForXLargeScreen.put(DisplayMetrics.DENSITY_HIGH, 96);
+            expectedMemorySizeForXLargeScreen.put(DisplayMetrics.DENSITY_260, 144);
             expectedMemorySizeForXLargeScreen.put(DisplayMetrics.DENSITY_280, 144);
+            expectedMemorySizeForXLargeScreen.put(DisplayMetrics.DENSITY_300, 144);
             expectedMemorySizeForXLargeScreen.put(DisplayMetrics.DENSITY_XHIGH, 192);
+            expectedMemorySizeForXLargeScreen.put(DisplayMetrics.DENSITY_340, 192);
             expectedMemorySizeForXLargeScreen.put(DisplayMetrics.DENSITY_360, 240);
             expectedMemorySizeForXLargeScreen.put(DisplayMetrics.DENSITY_400, 288);
             expectedMemorySizeForXLargeScreen.put(DisplayMetrics.DENSITY_420, 336);
diff --git a/tests/app/src/android/app/cts/SystemFeaturesTest.java b/tests/app/src/android/app/cts/SystemFeaturesTest.java
index cad4922..aca79fc 100644
--- a/tests/app/src/android/app/cts/SystemFeaturesTest.java
+++ b/tests/app/src/android/app/cts/SystemFeaturesTest.java
@@ -48,6 +48,8 @@
 import java.util.List;
 import java.util.Set;
 
+import junit.framework.AssertionFailedError;
+
 /**
  * Test for checking that the {@link PackageManager} is reporting the correct features.
  */
@@ -258,10 +260,18 @@
 
     public void testNfcFeatures() {
         if (NfcAdapter.getDefaultAdapter(mContext) != null) {
-            assertAvailable(PackageManager.FEATURE_NFC);
-            assertAvailable(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION);
+            // Watches MAY support all FEATURE_NFC features when an NfcAdapter is available, but
+            // non-watches MUST support them both.
+            if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)) {
+                assertOneAvailable(PackageManager.FEATURE_NFC,
+                    PackageManager.FEATURE_NFC_HOST_CARD_EMULATION);
+            } else {
+                assertAvailable(PackageManager.FEATURE_NFC);
+                assertAvailable(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION);
+            }
         } else {
             assertNotAvailable(PackageManager.FEATURE_NFC);
+            assertNotAvailable(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION);
         }
     }
 
@@ -498,6 +508,16 @@
                 mAvailableFeatures.contains(feature));
     }
 
+    private void assertOneAvailable(String feature1, String feature2) {
+        if ((mPackageManager.hasSystemFeature(feature1) && mAvailableFeatures.contains(feature1)) ||
+            (mPackageManager.hasSystemFeature(feature2) && mAvailableFeatures.contains(feature2))) {
+            return;
+        } else {
+            throw new AssertionFailedError("Must support at least one of " + feature1 + " or " +
+                feature2);
+        }
+    }
+
     private void assertFeature(boolean exist, String feature) {
         if (exist) {
             assertAvailable(feature);
diff --git a/tests/camera/src/android/hardware/camera2/cts/CameraTestUtils.java b/tests/camera/src/android/hardware/camera2/cts/CameraTestUtils.java
index cdca29b..0de3ae6 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CameraTestUtils.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CameraTestUtils.java
@@ -2179,8 +2179,16 @@
 
         // TAG_ISO
         int iso = exif.getAttributeInt(ExifInterface.TAG_ISO, /*defaultValue*/-1);
-        if (staticInfo.areKeysAvailable(CaptureResult.SENSOR_SENSITIVITY)) {
-            int expectedIso = result.get(CaptureResult.SENSOR_SENSITIVITY);
+        if (staticInfo.areKeysAvailable(CaptureResult.SENSOR_SENSITIVITY) ||
+                staticInfo.areKeysAvailable(CaptureResult.CONTROL_POST_RAW_SENSITIVITY_BOOST)) {
+            int expectedIso = 100;
+            if (staticInfo.areKeysAvailable(CaptureResult.SENSOR_SENSITIVITY)) {
+                expectedIso = result.get(CaptureResult.SENSOR_SENSITIVITY);
+            }
+            if (staticInfo.areKeysAvailable(CaptureResult.CONTROL_POST_RAW_SENSITIVITY_BOOST)) {
+                expectedIso = expectedIso *
+                        result.get(CaptureResult.CONTROL_POST_RAW_SENSITIVITY_BOOST) / 100;
+            }
             collector.expectEquals("Exif TAG_ISO is incorrect", expectedIso, iso);
         }
 
diff --git a/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java b/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java
index a367d37..3c762cf 100644
--- a/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java
@@ -451,17 +451,18 @@
      * validated.
      */
     private void previewFpsRangeTestByCamera() throws Exception {
-        Size maxPreviewSz = mOrderedPreviewSizes.get(0);
+        Size maxPreviewSz;
         Range<Integer>[] fpsRanges = mStaticInfo.getAeAvailableTargetFpsRangesChecked();
         boolean antiBandingOffIsSupported = mStaticInfo.isAntiBandingOffModeSupported();
         Range<Integer> fpsRange;
         CaptureRequest.Builder requestBuilder =
                 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
         SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
-        startPreview(requestBuilder, maxPreviewSz, resultListener);
 
         for (int i = 0; i < fpsRanges.length; i += 1) {
             fpsRange = fpsRanges[i];
+            maxPreviewSz = getMaxPreviewSizeForFpsRange(fpsRange);
+            startPreview(requestBuilder, maxPreviewSz, resultListener);
 
             requestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange);
             // Turn off auto antibanding to avoid exposure time and frame duration interference
@@ -482,9 +483,9 @@
 
             verifyPreviewTargetFpsRange(resultListener, NUM_FRAMES_VERIFIED, fpsRange,
                     maxPreviewSz);
+            stopPreview();
+            resultListener.drain();
         }
-
-        stopPreview();
     }
 
     private void verifyPreviewTargetFpsRange(SimpleCaptureCallback resultListener,
diff --git a/tests/tests/carrierapi/AndroidTest.xml b/tests/tests/carrierapi/AndroidTest.xml
index 2091d97..fa8f4cc 100644
--- a/tests/tests/carrierapi/AndroidTest.xml
+++ b/tests/tests/carrierapi/AndroidTest.xml
@@ -15,7 +15,7 @@
 -->
 <configuration description="Config for CTS Carrier APIs test cases">
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.TokenRequirement">
-        <option name="token" value="sim-card" />
+        <option name="token" value="sim-card-with-certs" />
     </target_preparer>
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.ApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java b/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
index a2dddb5..4452953 100644
--- a/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
+++ b/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
@@ -24,6 +24,7 @@
 import android.content.pm.ResolveInfo;
 import android.media.RingtoneManager;
 import android.net.Uri;
+import android.os.storage.StorageManager;
 import android.provider.AlarmClock;
 import android.provider.MediaStore;
 import android.provider.Settings;
@@ -242,4 +243,8 @@
     public void testViewDownloads() {
         assertCanBeHandled(new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS));
     }
+
+    public void testManageStorage() {
+        assertCanBeHandled(new Intent(StorageManager.ACTION_MANAGE_STORAGE));
+    }
 }
diff --git a/tests/tests/dpi/src/android/dpi/cts/ConfigurationTest.java b/tests/tests/dpi/src/android/dpi/cts/ConfigurationTest.java
index fc38fdd..78ce89a 100644
--- a/tests/tests/dpi/src/android/dpi/cts/ConfigurationTest.java
+++ b/tests/tests/dpi/src/android/dpi/cts/ConfigurationTest.java
@@ -59,8 +59,11 @@
         allowedDensities.add(DisplayMetrics.DENSITY_MEDIUM);
         allowedDensities.add(DisplayMetrics.DENSITY_TV);
         allowedDensities.add(DisplayMetrics.DENSITY_HIGH);
+        allowedDensities.add(DisplayMetrics.DENSITY_260);
         allowedDensities.add(DisplayMetrics.DENSITY_280);
+        allowedDensities.add(DisplayMetrics.DENSITY_300);
         allowedDensities.add(DisplayMetrics.DENSITY_XHIGH);
+        allowedDensities.add(DisplayMetrics.DENSITY_340);
         allowedDensities.add(DisplayMetrics.DENSITY_360);
         allowedDensities.add(DisplayMetrics.DENSITY_400);
         allowedDensities.add(DisplayMetrics.DENSITY_420);
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedVectorDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedVectorDrawableTest.java
index 487eb3b..5dabdfd 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedVectorDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedVectorDrawableTest.java
@@ -147,24 +147,25 @@
         mActivity.runOnUiThread(new Runnable() {
             @Override
             public void run() {
+                mActivity.setContentView(mLayoutId);
+                ImageView imageView = (ImageView) mActivity.findViewById(mImageViewId);
+                imageView.setImageDrawable(d1);
                 d1.start();
                 d1.stop();
-
+                d1.setBounds(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT);
+                mBitmap.eraseColor(0);
+                d1.draw(mCanvas);
             }
         });
+
         getInstrumentation().waitForIdleSync();
-
-        d1.setBounds(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT);
-        mBitmap.eraseColor(0);
-        d1.draw(mCanvas);
-
         int endColor = mBitmap.getPixel(IMAGE_WIDTH / 2, IMAGE_HEIGHT / 2);
 
-        assertEquals("Center point's color must be green", 0xFF00FF00, endColor);
-
         if (DBG_DUMP_PNG) {
             saveVectorDrawableIntoPNG(mBitmap, resId);
         }
+
+        assertEquals("Center point's color must be green", 0xFF00FF00, endColor);
     }
 
     @SmallTest
diff --git a/tests/tests/jni/libjnitest/android_jni_cts_LinkerNamespacesTest.cpp b/tests/tests/jni/libjnitest/android_jni_cts_LinkerNamespacesTest.cpp
index 8c74158..8a82706 100644
--- a/tests/tests/jni/libjnitest/android_jni_cts_LinkerNamespacesTest.cpp
+++ b/tests/tests/jni/libjnitest/android_jni_cts_LinkerNamespacesTest.cpp
@@ -45,10 +45,10 @@
 static const std::string kVendorLibraryPath = "/vendor/lib";
 #endif
 
-// This is not complete list - just a small subset
+// This is not the complete list - just a small subset
 // of the libraries that should reside in /system/lib
 // (in addition to kSystemPublicLibraries)
-static std::unordered_set<std::string> kSystemLibraries = {
+static std::vector<std::string> kSystemLibraries = {
     "libart.so",
     "libandroid_runtime.so",
     "libbinder.so",
@@ -61,39 +61,6 @@
     "libutils.so",
   };
 
-template <typename F>
-static bool for_each_file(const std::string& dir, F functor, std::string* error_msg) {
-  auto dir_deleter = [](DIR* handle) { closedir(handle); };
-  std::unique_ptr<DIR, decltype(dir_deleter)> dirp(opendir(dir.c_str()), dir_deleter);
-  if (dirp == nullptr) {
-    *error_msg = strerror(errno);
-    return false;
-  }
-
-  dirent* dp;
-  while ((dp = readdir(dirp.get())) != nullptr) {
-    // skip "." and ".."
-    if (strcmp(".", dp->d_name) == 0 ||
-        strcmp("..", dp->d_name) == 0) {
-      continue;
-    }
-
-    if (!functor(dp->d_name, error_msg)) {
-      return false;
-    }
-  }
-
-  return true;
-}
-
-static bool should_be_accessible(const std::string& public_library_path,
-                                 const std::unordered_set<std::string>& public_libraries,
-                                 const std::string& path) {
-  std::string name = basename(path.c_str());
-  return (public_libraries.find(name) != public_libraries.end()) &&
-         (public_library_path + "/" + name == path);
-}
-
 static bool is_directory(const std::string path) {
   struct stat sb;
   if (stat(path.c_str(), &sb) != -1) {
@@ -107,10 +74,18 @@
   return kSystemLibraryPath + "/libdl.so" == path;
 }
 
-static bool check_lib(const std::string& public_library_path,
-                      const std::unordered_set<std::string>& public_libraries,
-                      const std::string& path,
-                      std::string* error_msg) {
+static bool already_loaded(const std::string& library, const std::string& err) {
+  if (err.find("dlopen failed: library \"" + library + "\"") != 0 ||
+      err.find("is not accessible for the namespace \"classloader-namespace\"") == std::string::npos) {
+    return false;
+  }
+  return true;
+}
+
+static bool check_lib(const std::string& path,
+                      const std::string& library_path,
+                      const std::unordered_set<std::string>& libraries,
+                      std::vector<std::string>* errors) {
   if (is_libdl(path)) {
     // TODO (dimitry): we skip check for libdl.so because
     // 1. Linker will fail to check accessibility because it imposes as libdl.so (see http://b/27106625)
@@ -119,76 +94,118 @@
     return true;
   }
 
-  auto dlcloser = [](void* handle) { dlclose(handle); };
-  std::unique_ptr<void, decltype(dlcloser)> handle(dlopen(path.c_str(), RTLD_NOW), dlcloser);
-  if (should_be_accessible(public_library_path, public_libraries, path)) {
+  std::unique_ptr<void, int (*)(void*)> handle(dlopen(path.c_str(), RTLD_NOW), dlclose);
+
+  // The current restrictions on public libraries:
+  //  - It must exist only in the top level directory of "library_path".
+  //  - No library with the same name can be found in a sub directory.
+  //  - Each public library does not contain any directory components.
+
+  // Check if this library should be considered a public library.
+  std::string baselib = basename(path.c_str());
+  if (libraries.find(baselib) != libraries.end() &&
+      library_path + "/" + baselib == path) {
     if (handle.get() == nullptr) {
-      *error_msg = "The library \"" + path + "\" should be accessible but isn't: " + dlerror();
+      errors->push_back("The library \"" + path +
+                        "\" is a public library but it cannot be loaded: " + dlerror());
       return false;
     }
-  } else if (handle != nullptr) {
-    *error_msg = "The library \"" + path + "\" should not be accessible";
+  } else if (handle.get() != nullptr) {
+    errors->push_back("The library \"" + path + "\" is not a public library but it loaded.");
     return false;
   } else { // (handle == nullptr && !shouldBeAccessible(path))
     // Check the error message
     std::string err = dlerror();
-
-    if (err.find("dlopen failed: library \"" + path + "\"") != 0 ||
-        err.find("is not accessible for the namespace \"classloader-namespace\"") == std::string::npos) {
-      *error_msg = "unexpected dlerror: " + err;
+    if (!already_loaded(path, err)) {
+      errors->push_back("unexpected dlerror: " + err);
       return false;
     }
   }
   return true;
 }
 
-static bool check_libs(const std::string& public_library_path,
-                       const std::unordered_set<std::string>& public_libraries,
-                       const std::unordered_set<std::string>& mandatory_files,
-                       std::string* error) {
-  std::list<std::string> dirs;
-  dirs.push_back(public_library_path);
-
+static bool check_path(const std::string& library_path,
+                       const std::unordered_set<std::string> libraries,
+                       std::vector<std::string>* errors) {
+  bool success = true;
+  std::list<std::string> dirs = { library_path };
   while (!dirs.empty()) {
-    const auto dir = dirs.front();
+    std::string dir = dirs.front();
     dirs.pop_front();
-    bool success = for_each_file(dir, [&](const char* name, std::string* error_msg) {
-      std::string path = dir + "/" + name;
+
+    auto dir_deleter = [](DIR* handle) { closedir(handle); };
+    std::unique_ptr<DIR, decltype(dir_deleter)> dirp(opendir(dir.c_str()), dir_deleter);
+    if (dirp == nullptr) {
+      errors->push_back("Failed to open " + dir + ": " + strerror(errno));
+      success = false;
+      continue;
+    }
+
+    dirent* dp;
+    while ((dp = readdir(dirp.get())) != nullptr) {
+      // skip "." and ".."
+      if (strcmp(".", dp->d_name) == 0 || strcmp("..", dp->d_name) == 0) {
+        continue;
+      }
+
+      std::string path = dir + "/" + dp->d_name;
       if (is_directory(path)) {
         dirs.push_back(path);
-        return true;
-      }
-
-      return check_lib(public_library_path, public_libraries, path, error_msg);
-    }, error);
-
-    if (!success) {
-      return false;
-    }
-
-    // Check mandatory files - the grey list
-    for (const auto& name : mandatory_files) {
-      std::string path = public_library_path + "/" + name;
-      if (!check_lib(public_library_path, public_libraries, path, error)) {
-        return false;
+      } else if (!check_lib(path, library_path, libraries, errors)) {
+        success = false;
       }
     }
   }
 
-  return true;
+  return success;
 }
 
-static void jobject_array_to_set(JNIEnv* env,
+static bool jobject_array_to_set(JNIEnv* env,
                                  jobjectArray java_libraries_array,
-                                 std::unordered_set<std::string>* libraries) {
+                                 std::unordered_set<std::string>* libraries,
+                                 std::string* error_msg) {
+  error_msg->clear();
   size_t size = env->GetArrayLength(java_libraries_array);
+  bool success = true;
   for (size_t i = 0; i<size; ++i) {
     ScopedLocalRef<jstring> java_soname(
         env, (jstring) env->GetObjectArrayElement(java_libraries_array, i));
+    std::string soname(ScopedUtfChars(env, java_soname.get()).c_str());
 
-    ScopedUtfChars soname(env, java_soname.get());
-    libraries->insert(soname.c_str());
+    // Verify that the name doesn't contain any directory components.
+    if (soname.rfind('/') != std::string::npos) {
+      *error_msg += "\n---Illegal value, no directories allowed: " + soname;
+      continue;
+    }
+
+    // Check to see if the string ends in " 32" or " 64" to indicate the
+    // library is only public for one bitness.
+    size_t space_pos = soname.rfind(' ');
+    if (space_pos != std::string::npos) {
+      std::string type = soname.substr(space_pos + 1);
+      if (type != "32" && type != "64") {
+        *error_msg += "\n---Illegal value at end of line (only 32 or 64 allowed): " + soname;
+        success = false;
+        continue;
+      }
+#if defined(__LP64__)
+      if (type == "32") {
+        // Skip this, it's a 32 bit only public library.
+        continue;
+      }
+#else
+      if (type == "64") {
+        // Skip this, it's a 64 bit only public library.
+        continue;
+      }
+#endif
+      soname.resize(space_pos);
+    }
+
+    libraries->insert(soname);
   }
+
+  return success;
 }
 
 extern "C" JNIEXPORT jstring JNICALL
@@ -197,17 +214,55 @@
         jclass clazz __attribute__((unused)),
         jobjectArray java_system_public_libraries,
         jobjectArray java_vendor_public_libraries) {
-  std::string error;
-
+  bool success = true;
+  std::vector<std::string> errors;
+  std::string error_msg;
   std::unordered_set<std::string> vendor_public_libraries;
-  std::unordered_set<std::string> system_public_libraries;
-  std::unordered_set<std::string> empty_set;
-  jobject_array_to_set(env, java_vendor_public_libraries, &vendor_public_libraries);
-  jobject_array_to_set(env, java_system_public_libraries, &system_public_libraries);
+  if (!jobject_array_to_set(env, java_vendor_public_libraries, &vendor_public_libraries,
+                            &error_msg)) {
+    success = false;
+    errors.push_back("Errors in vendor public library file:" + error_msg);
+  }
 
-  if (!check_libs(kSystemLibraryPath, system_public_libraries, kSystemLibraries, &error) ||
-      !check_libs(kVendorLibraryPath, vendor_public_libraries, empty_set, &error)) {
-    return env->NewStringUTF(error.c_str());
+  std::unordered_set<std::string> system_public_libraries;
+  if (!jobject_array_to_set(env, java_system_public_libraries, &system_public_libraries,
+                            &error_msg)) {
+    success = false;
+    errors.push_back("Errors in system public library file:" + error_msg);
+  }
+
+  // Check the system libraries.
+  if (!check_path(kSystemLibraryPath, system_public_libraries, &errors)) {
+    success = false;
+  }
+
+  // Check that the mandatory system libraries are present - the grey list
+  for (const auto& name : kSystemLibraries) {
+    std::string library = kSystemLibraryPath + "/" + name;
+    void* handle = dlopen(library.c_str(), RTLD_NOW);
+    if (handle == nullptr) {
+      std::string err = dlerror();
+      // If the library is already loaded, then dlopen failing is okay.
+      if (!already_loaded(library, err)) {
+          errors.push_back("Mandatory system library \"" + library + "\" failed to load: " + err);
+          success = false;
+      }
+    } else {
+      dlclose(handle);
+    }
+  }
+
+  // Check the vendor libraries.
+  if (!check_path(kVendorLibraryPath, vendor_public_libraries, &errors)) {
+    success = false;
+  }
+
+  if (!success) {
+    std::string error_str;
+    for (const auto& line : errors) {
+      error_str += line + '\n';
+    }
+    return env->NewStringUTF(error_str.c_str());
   }
 
   return nullptr;
diff --git a/tests/tests/media/res/raw/color_176x144_bt601_525_lr_sdr_h264.mp4 b/tests/tests/media/res/raw/color_176x144_bt601_525_lr_sdr_h264.mp4
new file mode 100644
index 0000000..84701c3
--- /dev/null
+++ b/tests/tests/media/res/raw/color_176x144_bt601_525_lr_sdr_h264.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/color_176x144_bt601_525_lr_sdr_h265.mp4 b/tests/tests/media/res/raw/color_176x144_bt601_525_lr_sdr_h265.mp4
new file mode 100644
index 0000000..7d948d4
--- /dev/null
+++ b/tests/tests/media/res/raw/color_176x144_bt601_525_lr_sdr_h265.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/color_176x144_bt601_fr_sdr_h264.mp4 b/tests/tests/media/res/raw/color_176x144_bt601_625_fr_sdr_h264.mp4
similarity index 100%
rename from tests/tests/media/res/raw/color_176x144_bt601_fr_sdr_h264.mp4
rename to tests/tests/media/res/raw/color_176x144_bt601_625_fr_sdr_h264.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/color_176x144_bt601_625_fr_sdr_h265.mp4 b/tests/tests/media/res/raw/color_176x144_bt601_625_fr_sdr_h265.mp4
new file mode 100644
index 0000000..11025be
--- /dev/null
+++ b/tests/tests/media/res/raw/color_176x144_bt601_625_fr_sdr_h265.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/color_176x144_bt709_lr_sdr_h265.mp4 b/tests/tests/media/res/raw/color_176x144_bt709_lr_sdr_h265.mp4
new file mode 100644
index 0000000..dfd97fe
--- /dev/null
+++ b/tests/tests/media/res/raw/color_176x144_bt709_lr_sdr_h265.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/color_176x144_srgb_lr_sdr_h264.mp4 b/tests/tests/media/res/raw/color_176x144_srgb_lr_sdr_h264.mp4
new file mode 100644
index 0000000..2360277
--- /dev/null
+++ b/tests/tests/media/res/raw/color_176x144_srgb_lr_sdr_h264.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/color_176x144_srgb_lr_sdr_h265.mp4 b/tests/tests/media/res/raw/color_176x144_srgb_lr_sdr_h265.mp4
new file mode 100644
index 0000000..fe34c5b
--- /dev/null
+++ b/tests/tests/media/res/raw/color_176x144_srgb_lr_sdr_h265.mp4
Binary files differ
diff --git a/tests/tests/media/src/android/media/cts/DecoderTest.java b/tests/tests/media/src/android/media/cts/DecoderTest.java
index 0e61df6..a2bdbea 100755
--- a/tests/tests/media/src/android/media/cts/DecoderTest.java
+++ b/tests/tests/media/src/android/media/cts/DecoderTest.java
@@ -297,12 +297,14 @@
      * aspects contained in the color box and VUI for the test stream.
      * P = primaries, T = transfer, M = coeffs, R = range. '-' means
      * empty value.
-     *                                  |   colr       |    VUI
-     * --------------------------------------------------------------
-     *         File Name                |  P  T  M  R  |  P  T  M  R
-     * --------------------------------------------------------------
-     *  color_176x144_bt709_lr_sdr_h264 |  1  1  1  0  |  -  -  -  -
-     *  color_176x144_bt601_fr_sdr_h264 |  1  6  6  0  |  5  2  2  1
+     *                                      |     colr     |    VUI
+     * -------------------------------------------------------------------
+     *         File Name                    |  P  T  M  R  |  P  T  M  R
+     * -------------------------------------------------------------------
+     *  color_176x144_bt709_lr_sdr_h264     |  1  1  1  0  |  -  -  -  -
+     *  color_176x144_bt601_625_fr_sdr_h264 |  1  6  6  0  |  5  2  2  1
+     *  color_176x144_bt601_525_lr_sdr_h264 |  6  5  4  0  |  2  6  6  0
+     *  color_176x144_srgb_lr_sdr_h264      |  2  0  2  1  |  1  13 1  0
      */
     public void testH264ColorAspects() throws Exception {
         testColorAspects(
@@ -310,9 +312,52 @@
                 MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT709,
                 MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
         testColorAspects(
-                R.raw.color_176x144_bt601_fr_sdr_h264, 2 /* testId */,
+                R.raw.color_176x144_bt601_625_fr_sdr_h264, 2 /* testId */,
                 MediaFormat.COLOR_RANGE_FULL, MediaFormat.COLOR_STANDARD_BT601_PAL,
                 MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
+        testColorAspects(
+                R.raw.color_176x144_bt601_525_lr_sdr_h264, 3 /* testId */,
+                MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT601_NTSC,
+                MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
+        testColorAspects(
+                R.raw.color_176x144_srgb_lr_sdr_h264, 4 /* testId */,
+                MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT709,
+                2 /* MediaFormat.COLOR_TRANSFER_SRGB */);
+    }
+
+    /**
+     * Test ColorAspects of all the HEVC decoders. Decoders should handle
+     * the colors aspects presented in both the mp4 atom 'colr' and VUI
+     * in the bitstream correctly. The following table lists the color
+     * aspects contained in the color box and VUI for the test stream.
+     * P = primaries, T = transfer, M = coeffs, R = range. '-' means
+     * empty value.
+     *                                      |     colr     |    VUI
+     * -------------------------------------------------------------------
+     *         File Name                    |  P  T  M  R  |  P  T  M  R
+     * -------------------------------------------------------------------
+     *  color_176x144_bt709_lr_sdr_h265     |  1  1  1  0  |  -  -  -  -
+     *  color_176x144_bt601_625_fr_sdr_h265 |  1  6  6  0  |  5  2  2  1
+     *  color_176x144_bt601_525_lr_sdr_h265 |  6  5  4  0  |  2  6  6  0
+     *  color_176x144_srgb_lr_sdr_h265      |  2  0  2  1  |  1  13 1  0
+     */
+    public void testH265ColorAspects() throws Exception {
+        testColorAspects(
+                R.raw.color_176x144_bt709_lr_sdr_h265, 1 /* testId */,
+                MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT709,
+                MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
+        testColorAspects(
+                R.raw.color_176x144_bt601_625_fr_sdr_h265, 2 /* testId */,
+                MediaFormat.COLOR_RANGE_FULL, MediaFormat.COLOR_STANDARD_BT601_PAL,
+                MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
+        testColorAspects(
+                R.raw.color_176x144_bt601_525_lr_sdr_h265, 3 /* testId */,
+                MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT601_NTSC,
+                MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
+        testColorAspects(
+                R.raw.color_176x144_srgb_lr_sdr_h265, 4 /* testId */,
+                MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT709,
+                2 /* MediaFormat.COLOR_TRANSFER_SRGB */);
     }
 
     private void testColorAspects(
diff --git a/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java b/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
index a3a0118..0b38e1b 100644
--- a/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
@@ -87,6 +87,10 @@
         assertEquals("Year was other than expected",
                 "2013", mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_YEAR));
 
+        assertEquals("Date was other than expected",
+                "19040101T000000.000Z",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DATE));
+
         assertNull("Writer was unexpected present",
                 mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_WRITER));
     }
@@ -112,6 +116,10 @@
         assertEquals("Year was other than expected",
                 "2013", mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_YEAR));
 
+        assertEquals("Date was other than expected",
+                "19700101T000000.000Z",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DATE));
+
         assertNull("Writer was unexpectedly present",
                 mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_WRITER));
     }
diff --git a/tests/tests/mediastress/preconditions/src/android/mediastress/cts/preconditions/MediaPreparer.java b/tests/tests/mediastress/preconditions/src/android/mediastress/cts/preconditions/MediaPreparer.java
index a9aabcf..8bae3a4 100644
--- a/tests/tests/mediastress/preconditions/src/android/mediastress/cts/preconditions/MediaPreparer.java
+++ b/tests/tests/mediastress/preconditions/src/android/mediastress/cts/preconditions/MediaPreparer.java
@@ -29,11 +29,7 @@
 import com.android.tradefed.util.ZipUtil;
 
 import java.awt.Dimension;
-import java.io.BufferedOutputStream;
 import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
 import java.io.InputStream;
 import java.io.IOException;
 import java.net.URL;
@@ -103,8 +99,8 @@
      *
      * These fields are exposed for unit testing
      */
-    protected String baseDeviceShortDir;
-    protected String baseDeviceFullDir;
+    protected String mBaseDeviceShortDir;
+    protected String mBaseDeviceFullDir;
 
     /*
      * Returns a string representation of the dimension
@@ -181,8 +177,8 @@
                 break; // we don't need to check for resolutions greater than or equal to this
             }
             String resString = resolutionString(copiedResolution);
-            String deviceShortFilePath = baseDeviceShortDir + resString;
-            String deviceFullFilePath = baseDeviceFullDir + resString;
+            String deviceShortFilePath = mBaseDeviceShortDir + resString;
+            String deviceFullFilePath = mBaseDeviceFullDir + resString;
             if (!device.doesFileExist(deviceShortFilePath) ||
                     !device.doesFileExist(deviceFullFilePath)) { // media files must be copied
                 return false;
@@ -205,10 +201,9 @@
         String[] subDirs = mediaFolder.list();
         if (subDirs.length != 1) {
             throw new TargetSetupError(String.format(
-                    "Unexpected contents in directory %s", mLocalMediaPath));
+                    "Unexpected contents in directory %s", mediaFolder.getAbsolutePath()));
         }
-        File newMediaFolder = new File(mediaFolder, subDirs[0]);
-        mLocalMediaPath = newMediaFolder.toString();
+        mLocalMediaPath = new File(mediaFolder, subDirs[0]).getAbsolutePath();
     }
 
     /*
@@ -216,47 +211,35 @@
      * Updates mLocalMediaPath to be the pathname of the directory containing bbb_short and
      * bbb_full media directories.
      */
-    private void downloadMediaToHost() throws TargetSetupError {
+    private void downloadMediaToHost(File mediaFolder) throws TargetSetupError {
 
         URL url;
         try {
+            // Get download URL from dynamic configuration service
             DynamicConfigHostSide config = new DynamicConfigHostSide(DYNAMIC_CONFIG_MODULE);
             String mediaUrlString = config.getValue(MEDIA_FILES_URL_KEY);
             url = new URL(mediaUrlString);
         } catch (IOException | XmlPullParserException e) {
             throw new TargetSetupError("Trouble finding media file download location with " +
-                    "dynamic configuration");
+                    "dynamic configuration", e);
         }
 
-        File mediaFolder = new File(mLocalMediaPath);
-        File mediaFolderZip = new File(mediaFolder.getName() + ".zip");
+        File mediaFolderZip = new File(mediaFolder.getAbsolutePath() + ".zip");
         try {
-
-            mediaFolder.mkdirs();
-            mediaFolderZip.createNewFile();
-
+            logInfo("Downloading media files from %s", url.toString());
             URLConnection conn = url.openConnection();
             InputStream in = conn.getInputStream();
-            BufferedOutputStream out =
-                    new BufferedOutputStream(new FileOutputStream(mediaFolderZip));
-            byte[] buffer = new byte[1024];
-            int count;
-            logInfo("Downloading media files to host from %s", url.toString());
-            while ((count = in.read(buffer)) >= 0) {
-                out.write(buffer, 0, count);
-            }
-            out.flush();
-            out.close();
-            in.close();
-
+            mediaFolderZip.createNewFile();
+            FileUtil.writeToFile(in, mediaFolderZip);
             logInfo("Unzipping media files");
             ZipUtil.extractZip(new ZipFile(mediaFolderZip), mediaFolder);
 
         } catch (IOException e) {
             FileUtil.recursiveDelete(mediaFolder);
-            FileUtil.recursiveDelete(mediaFolderZip);
             throw new TargetSetupError("Failed to download and open media files on host, the"
-                    + " device requires these media files for CTS media tests");
+                    + " device requires these media files for CTS media tests", e);
+        } finally {
+            FileUtil.deleteFile(mediaFolderZip);
         }
     }
 
@@ -280,8 +263,8 @@
                         resString);
                 return;
             }
-            String deviceShortFilePath = baseDeviceShortDir + resString;
-            String deviceFullFilePath = baseDeviceFullDir + resString;
+            String deviceShortFilePath = mBaseDeviceShortDir + resString;
+            String deviceFullFilePath = mBaseDeviceFullDir + resString;
             if (!device.doesFileExist(deviceShortFilePath) ||
                     !device.doesFileExist(deviceFullFilePath)) {
                 logInfo("Copying files of resolution %s to device", resString);
@@ -305,8 +288,8 @@
     // Initialize directory strings where media files live on device
     protected void setMountPoint(ITestDevice device) {
         String mountPoint = device.getMountPoint(IDevice.MNT_EXTERNAL_STORAGE);
-        baseDeviceShortDir = String.format("%s/test/bbb_short/", mountPoint);
-        baseDeviceFullDir = String.format("%s/test/bbb_full/", mountPoint);
+        mBaseDeviceShortDir = String.format("%s/test/bbb_short/", mountPoint);
+        mBaseDeviceFullDir = String.format("%s/test/bbb_full/", mountPoint);
     }
 
     @Override
@@ -314,8 +297,7 @@
             BuildError, DeviceNotAvailableException {
 
         if (mSkipMediaDownload) {
-            // skip this precondition
-            return;
+            return; // skip this precondition
         }
 
         setMountPoint(device);
@@ -326,25 +308,18 @@
             return;
         }
 
-        File mediaFolder;
         if (mLocalMediaPath == null) {
             // Option 'local-media-path' has not been defined
-            try {
-                // find system's temp directory, create folder MEDIA_FOLDER_NAME inside
-                File tmpFile = File.createTempFile(MEDIA_FOLDER_NAME, null);
-                String tmpDir = tmpFile.getParent();
-                mediaFolder = new File(tmpDir, MEDIA_FOLDER_NAME);
-                // delete temp file used for locating temp directory
-                tmpFile.delete();
-            } catch (IOException e) {
-                throw new TargetSetupError("Unable to create host temp directory for media files");
+            // Get directory to store media files on this host
+            File mediaFolder = new File(System.getProperty("java.io.tmpdir"), MEDIA_FOLDER_NAME);
+            if(!mediaFolder.exists() || mediaFolder.list().length == 0){
+                // If directory already exists and contains files, it has been created by previous
+                // runs of MediaPreparer. Assume media files exist inside.
+                // Else, create directory if needed and download/extract media files inside.
+                mediaFolder.mkdirs();
+                downloadMediaToHost(mediaFolder);
             }
-            mLocalMediaPath = mediaFolder.getAbsolutePath();
-            if(!mediaFolder.exists()){
-                // directory has not been created by previous runs of MediaPreparer
-                // download media into mLocalMediaPath
-                downloadMediaToHost();
-            }
+            // set mLocalMediaPath to where the CTS media files have been extracted
             updateLocalMediaPath(mediaFolder);
         }
 
diff --git a/tests/tests/mediastress/preconditions/tests/src/android/mediastress/cts/preconditions/MediaPreparerTest.java b/tests/tests/mediastress/preconditions/tests/src/android/mediastress/cts/preconditions/MediaPreparerTest.java
index 54ab025..4f71069 100644
--- a/tests/tests/mediastress/preconditions/tests/src/android/mediastress/cts/preconditions/MediaPreparerTest.java
+++ b/tests/tests/mediastress/preconditions/tests/src/android/mediastress/cts/preconditions/MediaPreparerTest.java
@@ -53,8 +53,8 @@
                 "/sdcard").once();
         EasyMock.replay(mMockDevice);
         mMediaPreparer.setMountPoint(mMockDevice);
-        assertEquals(mMediaPreparer.baseDeviceShortDir, "/sdcard/test/bbb_short/");
-        assertEquals(mMediaPreparer.baseDeviceFullDir, "/sdcard/test/bbb_full/");
+        assertEquals(mMediaPreparer.mBaseDeviceShortDir, "/sdcard/test/bbb_short/");
+        assertEquals(mMediaPreparer.mBaseDeviceFullDir, "/sdcard/test/bbb_full/");
     }
 
     public void testCopyMediaFiles() throws Exception {
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/Android.mk b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/Android.mk
new file mode 100644
index 0000000..3993d00
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/Android.mk
@@ -0,0 +1,34 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_PACKAGE_NAME := CtsNetSecConfigDownloadManagerTestCases
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner org.apache.http.legacy android-support-test
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES += $(call all-java-files-under, ../src)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res/
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_SDK_VERSION := current
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/AndroidManifest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/AndroidManifest.xml
new file mode 100644
index 0000000..e18ff4d
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="android.security.net.config.cts.CtsNetSecConfigDownloadManagerTestCases">
+  <application android:networkSecurityConfig="@xml/network_security_config">
+      <uses-library android:name="android.test.runner"/>
+  </application>
+
+  <uses-permission android:name="android.permission.INTERNET" />
+  <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                   android:targetPackage="android.security.net.config.cts.CtsNetSecConfigDownloadManagerTestCases"
+                   android:label="">
+        <meta-data android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+</manifest>
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/AndroidTest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/AndroidTest.xml
new file mode 100644
index 0000000..e966baa
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/AndroidTest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for CTS CtsNetSecConfigDownloadManagerTestCases test cases">
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.ApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsNetSecConfigDownloadManagerTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.security.net.config.cts.CtsNetSecConfigDownloadManagerTestCases" />
+    </test>
+</configuration>
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/res/raw/invalid_chain.pem b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/res/raw/invalid_chain.pem
new file mode 100644
index 0000000..ed33e7f
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/res/raw/invalid_chain.pem
@@ -0,0 +1,35 @@
+-----BEGIN CERTIFICATE-----
+MIICvjCCAaagAwIBAgIDDUD7MA0GCSqGSIb3DQEBCwUAMCMxITAfBgNVBAMTGEFu
+ZHJvaWQgQ1RTIHVudHJ1c3RlZCBDQTAqGBMyMDE1MDEwMTAwMDAwMCswMDAwGBMy
+MDI1MDEwMTAwMDAwMCswMDAwMBQxEjAQBgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJ
+KoZIhvcNAQEBBQADggEPADCCAQoCggEBAKH+IdnzoDeGaE1SWn4L8dhfd59AJVYO
+Wxz88ntK55iPCxPGdNdqL5MX0On9K0iBf+e839mRMIdjY1BtEo5Ln9MA6RTLQwt6
+OPaWF/HQQHkmrLOOShNZcerea+rJHPfN7NedSg6Ufb2bcVn7DrKBwUigAJDWVn02
+IB6wHO9slF+NsAcpyecxtvY/p7t0lguAe0j1IiVfX+xGdNFU7WjmGRQzk5KavFi3
+BwDc25rXP7JJ/6M66TnzI54iRI918P0AbhE+3K/5Bbe8qPFtdlEOChP6npUW1Nhm
+z99KolkcW/uCXUBHAsm27QPdW3wYX6hwa5eS8VGTWuhEOddPdBvGGPcCAwEAATAN
+BgkqhkiG9w0BAQsFAAOCAQEAgABDM5HU2+XE6Hx/Ti8LpnJXLdNk6Y1uZro2Vvmz
+MqwdKBC/k5RrdIyalN5lZzCRWKi4f4wgWGGnqbxlAugwa5N0+LWgw2Em4W8HEk6c
+DK9TPVnh7y87ibwGmyeU+bHMyFuVV8Yp+tXUCV2aQhM/yBEyCOEei/twWeZ7uVaw
+ANraJ0UDDeznqJX3rTsvwwBfKLmFm98YhzB3EYVo332oCuvC90RLmEerI5JmpNAw
+jg6Z0DMShcfdN2kIW1NEUTGBbd5sGsPRJVba0giEwXtDKorPLe+kJJMzji8HRk0x
+51tpxEseBrS3DjiIS7CT1RuiBfVJAdfzOHyDeFCX9t7tFQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIC4jCCAcqgAwIBAgIDBxMgMA0GCSqGSIb3DQEBCwUAMCMxITAfBgNVBAMTGEFu
+ZHJvaWQgQ1RTIHVudHJ1c3RlZCBDQTAqGBMyMDE1MDEwMTAwMDAwMCswMDAwGBMy
+MDI1MDEwMTAwMDAwMCswMDAwMCMxITAfBgNVBAMTGEFuZHJvaWQgQ1RTIHVudHJ1
+c3RlZCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMWCkHZZHBs7
+m1njBgF2yh4zEHOO1jN3nl9tNJwXWK6O3qAr4UC/CbokIu4onG26I9kHbaCAcM3L
+7qmuz2cL5gqSrwUD7nVC38+EnP8WpMt/SFljJYlbNqGMep8/ZvybtK8wJm+dAY3w
+Cj4vU9w9XPakG6m0FkSLtS5+XaAIM0rRbWGcPWBv+nHOwXBNpggoe63L2uJ6wra7
+NwW0epXT4FuMzY+f3/ZSdNbhMs4/gJbLHYMt81w7YZ2DY/fgGbZGjLc6PQvV8bZb
++Wib/Lg0o2rFb9O+pdU0azZQ/kyD/+CBjuEewJCcl6dsQX5k8A71di4uWBHaopVr
+gN2MTL2pqRECAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsF
+AAOCAQEAORmso2dZmTmaUHQKRnbpDoVcUDeDJWSnkgCbP1ZgpQ+B7t5ZWnLXqpVy
+eyK/ENscNPMpbyyQ8eaeydpSD6jipJfmH3O8NhtWZPA/1oY0Wm4/lsosZGFSadWg
+nSLfqxZtBy+VIZBGZrhPhlJ2U2WKmrTaMYS7TJy1t9RcQIw79pnnLKXAAhZx72U5
+FtPMAGREDaFMt7pVcM63ipytUPtrXH6nzOFHmsGGT0sbA0+/QkN5NkYYbHbFP6oI
+BJ4xZHVLCoyt+5kscsIZXsLb6jd1d/8RoD1w+559uE3T5AyPmfGRnq9+QjKbf0hx
+MC5lBV/nTWSf+GM0Q/hy2CPvvB7WNA==
+-----END CERTIFICATE-----
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/res/raw/test_key.pkcs8 b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/res/raw/test_key.pkcs8
new file mode 100644
index 0000000..c4e2d08
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/res/raw/test_key.pkcs8
Binary files differ
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/res/raw/valid_ca.pem b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/res/raw/valid_ca.pem
new file mode 100644
index 0000000..d70b4d6
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/res/raw/valid_ca.pem
@@ -0,0 +1,46 @@
+-----BEGIN CERTIFICATE-----
+MIIC3jCCAcagAwIBAgIDDGqSMA0GCSqGSIb3DQEBCwUAMCExHzAdBgNVBAMTFkFu
+ZHJvaWQgQ1RTIHRydXN0ZWQgQ0EwKhgTMjAxNTAxMDEwMDAwMDArMDAwMBgTMjAy
+NTAxMDEwMDAwMDArMDAwMDAhMR8wHQYDVQQDExZBbmRyb2lkIENUUyB0cnVzdGVk
+IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyu1Eg5wKieyro7qL
+XIx+qaqbE8mPqRL81i0mtQBjnq3gsXV3f7okUssg/8QRzYiYGP/shly70MOqKURP
+/gl7OtUj8SXLwQFzZ6B9hnWTXGRnBY4JFcgSy6LJMwo+ZPgwVtbjf1DAWNOLRhqY
+J9Uxr0PX5KZ5AafFVh0Y+JVmaFfGPxJ/UBi83GQ7ToKBvHTFN5SQjg5QtlW5DaEN
+cbO7lzB/OuKnIlLP6WlEVwCS+cToZAzaTafOVZaUarWHit0kq+8xyxl+koxgLcCK
+lkDYpZCezY3UAxGheRnmSuah6LK9BRx2cSMOKkeN3sAoVB6ARi7F30MYj7RH2XRz
+LumXLQIDAQABoxMwETAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IB
+AQBJi4SF/k1KVUZrweqUZ/QJ5HBDxWo4kE4iNw415qw2fAfNsKJknH1bcqgHa7Ea
+nokT8a1KOQlicInptNRbkwBd3Xakt9k9aCWRqyqBzZZersakZ1cB3SNxameelGzl
+a3dvGqVreE3LWhiQR7A3g84hS1kH5oNiY6GVZRk8BsmUUsvKaS6FJSMb9bAGSijQ
+EZwsBk+HoSuLSVxUDtLZgbs1NYVK8jCG6GPv8cWis03pK3VKqjTi3DDs7mHioViG
+G/TUZPq5ok8BemctNPLZAMLVlWPVB389iTOmgJWdR2Lu7LKh4B952+SeHMo3huUR
+Hn/e+Sq5FmJfDVvFG6U3PEDd
+-----END CERTIFICATE-----
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDK7USDnAqJ7Kuj
+uotcjH6pqpsTyY+pEvzWLSa1AGOereCxdXd/uiRSyyD/xBHNiJgY/+yGXLvQw6op
+RE/+CXs61SPxJcvBAXNnoH2GdZNcZGcFjgkVyBLLoskzCj5k+DBW1uN/UMBY04tG
+Gpgn1TGvQ9fkpnkBp8VWHRj4lWZoV8Y/En9QGLzcZDtOgoG8dMU3lJCODlC2VbkN
+oQ1xs7uXMH864qciUs/paURXAJL5xOhkDNpNp85VlpRqtYeK3SSr7zHLGX6SjGAt
+wIqWQNilkJ7NjdQDEaF5GeZK5qHosr0FHHZxIw4qR43ewChUHoBGLsXfQxiPtEfZ
+dHMu6ZctAgMBAAECggEAezX1E7P68iOxU4hAdcEYZwwffLQ1dgMBYUmo5t2FnyMT
++qvIEtWCmIKdVq5F4PW+4+8APdSwdOFYwBWqPCSlneMsH49DV7z5xUG89ZcOElsj
+8kt7WK5SOzJr14GwwL2xHAj9uJ/fKg/H0Jj1KbpYoIIg48PwVQD44IBqWQTdWRxd
+QVbxczDIHAjXSD14P4uUAXQrFyYEQXgksu4FNNGFr6JnuNe6eSreKxrw8/7J9OXZ
+7VUfN0Iuw/M4HF1dKQKVK2R0W34wuS2KyI3fKUS7RoSrfXfBuZ1hQ1gWoATiXkbR
+AAMUSWuaj5RQ4lj0wxdRAO+e4QB2yUXHgzCr8pH6QQKBgQDuiXtcdZ2FVN9ezxJt
+XDd6225Rvh8XtWEUwTaJmOtZz2AKlKTQr06u/BqqpKWc5SWQSf88K7WPxF6EMizB
+4D3wVGzCFkeRMMriZmrRe+8IVCq+mAZnRahV4SSH35ZQoNd8/3Mv6o59/UR0x7Nl
+5yTqruROK0Ycz8S0GlvfKiDyywKBgQDZyGaIYqZ63piagmRx3EB1Z+8yfXnn8g2d
+iVYU3UTDWxAFtzq6cfPRUdDxGHgAjmVmLvSGEaxqYNOftxwC3zk1E03w4/KvXg+y
+Vt+1qPZ7Hj1OcGMYA+1/Qy6+GMneYnUkmO9zHoNzSDG5hfNkQ+3SyMx53FfTO8oA
+Lrpl4gFG5wKBgQCtCGXIKDlf4rU13RgM5HwKTuqzuSps1FHb8FxTa+4tc9TDWBhG
+mSSGorHlXxITwdWB2WughkRqSZQWaR82dCf6EgPitq6rj61cldaepzw52nQ3Vagv
+ecQmp+8L8RDk5Afs0JEKDSfYFMR3wfVM0mNhKgTK/3EYrU6PJx/FvpWwCQKBgDrk
+ICXdV1t+ehG+FN9dSej1tA8ZMy/vmpLxIl/9/aw+IbUJ+U2VpvMBhtjLXxf3aaAa
+LnFash8KE+/qmh6EsnmRwM/VNDkL3H7DUzdSe2SLptRhO8qwtTZmumsZVO1X/olo
++cdNhwpTiW67tDd2zwbi2bhSR0WNs3AdMrZ+SQ4dAoGBANkjgWwzVN8KGOe9GdEo
+opcwVzC1l9xkUcB6ykIG+DKw5p1ChGLA+2uufdpNWfPqXixCt5X3qIOy1q/VIdlj
+EHNurGEld93H86V0ieLMRPg5llXWfKND2W8vezZSCGqFcSo+bAVi0YzA6XbLu+TV
+GyyCD8Jk/efmdN0DKjERIKDH
+-----END PRIVATE KEY-----
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/res/raw/valid_chain.pem b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/res/raw/valid_chain.pem
new file mode 100644
index 0000000..e974055
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/res/raw/valid_chain.pem
@@ -0,0 +1,35 @@
+-----BEGIN CERTIFICATE-----
+MIICvDCCAaSgAwIBAgIDAQdvMA0GCSqGSIb3DQEBCwUAMCExHzAdBgNVBAMTFkFu
+ZHJvaWQgQ1RTIHRydXN0ZWQgQ0EwKhgTMjAxNTAxMDEwMDAwMDArMDAwMBgTMjAy
+NTAxMDEwMDAwMDArMDAwMDAUMRIwEAYDVQQDEwlsb2NhbGhvc3QwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCh/iHZ86A3hmhNUlp+C/HYX3efQCVWDlsc
+/PJ7SueYjwsTxnTXai+TF9Dp/StIgX/nvN/ZkTCHY2NQbRKOS5/TAOkUy0MLejj2
+lhfx0EB5JqyzjkoTWXHq3mvqyRz3zezXnUoOlH29m3FZ+w6ygcFIoACQ1lZ9NiAe
+sBzvbJRfjbAHKcnnMbb2P6e7dJYLgHtI9SIlX1/sRnTRVO1o5hkUM5OSmrxYtwcA
+3Nua1z+ySf+jOuk58yOeIkSPdfD9AG4RPtyv+QW3vKjxbXZRDgoT+p6VFtTYZs/f
+SqJZHFv7gl1ARwLJtu0D3Vt8GF+ocGuXkvFRk1roRDnXT3Qbxhj3AgMBAAEwDQYJ
+KoZIhvcNAQELBQADggEBALSYVeSPW5+SHhMiOo6tvF0BDKCVqFwmggdpgxM660mp
+NXLoStfFNZSNkcWsLDsGPQC1RXuHDWq7GmMpaKGRtQDnVrArGvPTRn4iJIRQZ+/F
+uMBllitSPCoae4qcFfwEXotO+OuLZsaaBd/r58F0NqDmX/UOY9EP7NulyuUZmeBK
+OOaeOKOln6BXQoMWp5irEd8tGBze1sOJLWNejquyViWuCTHvHlafBi0fM3jAE5dL
+pnXStZFOCzU2J/DnfbLJmwbScYtYc9FiB3jMLM6vZZGGXT5Go3uiffcOAFSkJgxx
+l0BCRobyePW7RdxULA8THkg9tm044I28EeJEkKmaWrQ=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIC3jCCAcagAwIBAgIDDGqSMA0GCSqGSIb3DQEBCwUAMCExHzAdBgNVBAMTFkFu
+ZHJvaWQgQ1RTIHRydXN0ZWQgQ0EwKhgTMjAxNTAxMDEwMDAwMDArMDAwMBgTMjAy
+NTAxMDEwMDAwMDArMDAwMDAhMR8wHQYDVQQDExZBbmRyb2lkIENUUyB0cnVzdGVk
+IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyu1Eg5wKieyro7qL
+XIx+qaqbE8mPqRL81i0mtQBjnq3gsXV3f7okUssg/8QRzYiYGP/shly70MOqKURP
+/gl7OtUj8SXLwQFzZ6B9hnWTXGRnBY4JFcgSy6LJMwo+ZPgwVtbjf1DAWNOLRhqY
+J9Uxr0PX5KZ5AafFVh0Y+JVmaFfGPxJ/UBi83GQ7ToKBvHTFN5SQjg5QtlW5DaEN
+cbO7lzB/OuKnIlLP6WlEVwCS+cToZAzaTafOVZaUarWHit0kq+8xyxl+koxgLcCK
+lkDYpZCezY3UAxGheRnmSuah6LK9BRx2cSMOKkeN3sAoVB6ARi7F30MYj7RH2XRz
+LumXLQIDAQABoxMwETAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IB
+AQBJi4SF/k1KVUZrweqUZ/QJ5HBDxWo4kE4iNw415qw2fAfNsKJknH1bcqgHa7Ea
+nokT8a1KOQlicInptNRbkwBd3Xakt9k9aCWRqyqBzZZersakZ1cB3SNxameelGzl
+a3dvGqVreE3LWhiQR7A3g84hS1kH5oNiY6GVZRk8BsmUUsvKaS6FJSMb9bAGSijQ
+EZwsBk+HoSuLSVxUDtLZgbs1NYVK8jCG6GPv8cWis03pK3VKqjTi3DDs7mHioViG
+G/TUZPq5ok8BemctNPLZAMLVlWPVB389iTOmgJWdR2Lu7LKh4B952+SeHMo3huUR
+Hn/e+Sq5FmJfDVvFG6U3PEDd
+-----END CERTIFICATE-----
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/res/xml/network_security_config.xml b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/res/xml/network_security_config.xml
new file mode 100644
index 0000000..6c064f9
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/res/xml/network_security_config.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+  <domain-config>
+    <domain>localhost</domain>
+    <trust-anchors>
+      <certificates src="@raw/valid_ca" />
+    </trust-anchors>
+  </domain-config>
+</network-security-config>
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/src/android/security/net/config/cts/DownloadManagerTest.java b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/src/android/security/net/config/cts/DownloadManagerTest.java
new file mode 100644
index 0000000..28c8eb8
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/src/android/security/net/config/cts/DownloadManagerTest.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.net.config.cts;
+
+import android.security.net.config.cts.CtsNetSecConfigDownloadManagerTestCases.R;
+
+import android.app.DownloadManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.SystemClock;
+import android.test.AndroidTestCase;
+import android.text.format.DateUtils;
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.net.Socket;
+import java.security.KeyFactory;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.Security;
+import java.security.Signature;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+
+public class DownloadManagerTest extends AndroidTestCase {
+
+    private static final String HTTP_RESPONSE =
+            "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\nContent-length: 5\r\n\r\nhello";
+    private static final long TIMEOUT = 3 * DateUtils.SECOND_IN_MILLIS;
+
+    public void testConfigTrustedCaAccepted() throws Exception {
+        runDownloadManagerTest(R.raw.valid_chain, R.raw.test_key);
+    }
+
+    public void testUntrustedCaRejected() throws Exception {
+        try {
+            runDownloadManagerTest(R.raw.invalid_chain, R.raw.test_key);
+            fail("Invalid CA should be rejected");
+        } catch (Exception expected) {
+        }
+    }
+
+    private void runDownloadManagerTest(int chainResId, int keyResId) throws Exception {
+        DownloadManager dm =
+                (DownloadManager) getContext().getSystemService(Context.DOWNLOAD_SERVICE);
+        DownloadCompleteReceiver receiver = new DownloadCompleteReceiver();
+        final SSLServerSocket serverSocket = bindTLSServer(chainResId, keyResId);
+        FutureTask<Void> serverFuture = new FutureTask<Void>(new Callable() {
+            @Override
+            public Void call() throws Exception {
+                runServer(serverSocket);
+                return null;
+            }
+        });
+        try {
+            IntentFilter filter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
+            getContext().registerReceiver(receiver, filter);
+            new Thread(serverFuture).start();
+            Uri destination = Uri.parse("https://localhost:" + serverSocket.getLocalPort());
+            long id = dm.enqueue(new DownloadManager.Request(destination));
+            try {
+                serverFuture.get(TIMEOUT, TimeUnit.MILLISECONDS);
+                // Check that the download was successful.
+                receiver.waitForDownloadComplete(TIMEOUT, id);
+                assertSuccessfulDownload(id);
+            } catch (InterruptedException e) {
+                // Wrap InterruptedException since otherwise it gets eaten by AndroidTest
+                throw new RuntimeException(e);
+            } finally {
+                dm.remove(id);
+            }
+        } finally {
+            getContext().unregisterReceiver(receiver);
+            serverFuture.cancel(true);
+            try {
+                serverSocket.close();
+            } catch (Exception ignored) {}
+        }
+    }
+
+    private void runServer(SSLServerSocket server) throws Exception {
+        Socket s = server.accept();
+        s.getOutputStream().write(HTTP_RESPONSE.getBytes());
+        s.getOutputStream().flush();
+        s.close();
+    }
+
+    private SSLServerSocket bindTLSServer(int chainResId, int keyResId) throws Exception {
+        // Load certificate chain.
+        CertificateFactory fact = CertificateFactory.getInstance("X.509");
+        Collection<? extends Certificate> certs;
+        try (InputStream is = getContext().getResources().openRawResource(chainResId)) {
+            certs = fact.generateCertificates(is);
+        }
+        X509Certificate[] chain = new X509Certificate[certs.size()];
+        int i = 0;
+        for (Certificate cert : certs) {
+            chain[i++] = (X509Certificate) cert;
+        }
+
+        // Load private key for the leaf.
+        PrivateKey key;
+        try (InputStream is = getContext().getResources().openRawResource(keyResId)) {
+            ByteArrayOutputStream keyout = new ByteArrayOutputStream();
+            byte[] buffer = new byte[4096];
+            int chunk_size;
+            while ((chunk_size = is.read(buffer)) != -1) {
+                keyout.write(buffer, 0, chunk_size);
+            }
+            is.close();
+            byte[] keyBytes = keyout.toByteArray();
+            key = KeyFactory.getInstance("RSA")
+                    .generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
+        }
+
+        // Create KeyStore based on the private key/chain.
+        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
+        ks.load(null);
+        ks.setKeyEntry("name", key, null, chain);
+
+        // Create SSLContext.
+        TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");
+        tmf.init(ks);
+        KeyManagerFactory kmf =
+                KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+        kmf.init(ks, null);
+        SSLContext context = SSLContext.getInstance("TLS");
+        context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
+
+        SSLServerSocket s = (SSLServerSocket) context.getServerSocketFactory().createServerSocket();
+        s.bind(null);
+        return s;
+    }
+
+    private void assertSuccessfulDownload(long id) throws Exception {
+        Cursor cursor = null;
+        DownloadManager dm =
+                (DownloadManager) getContext().getSystemService(Context.DOWNLOAD_SERVICE);
+        try {
+            cursor = dm.query(new DownloadManager.Query().setFilterById(id));
+            assertTrue(cursor.moveToNext());
+            assertEquals(DownloadManager.STATUS_SUCCESSFUL, cursor.getInt(
+                    cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)));
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+    }
+
+    private static final class DownloadCompleteReceiver extends BroadcastReceiver {
+        private HashSet<Long> mCompletedDownloads = new HashSet<>();
+
+        public DownloadCompleteReceiver() {
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            synchronized(mCompletedDownloads) {
+                mCompletedDownloads.add(intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1));
+                mCompletedDownloads.notifyAll();
+            }
+        }
+
+        public void waitForDownloadComplete(long timeout, long id)
+                throws TimeoutException, InterruptedException  {
+            long deadline = SystemClock.elapsedRealtime() + timeout;
+            do {
+                synchronized (mCompletedDownloads) {
+                    long millisTillTimeout = deadline - SystemClock.elapsedRealtime();
+                    if (millisTillTimeout > 0) {
+                        mCompletedDownloads.wait(millisTillTimeout);
+                    }
+                    if (mCompletedDownloads.contains(id)) {
+                        return;
+                    }
+                }
+            } while (SystemClock.elapsedRealtime() < deadline);
+
+            throw new TimeoutException("Timed out waiting for download complete");
+        }
+    }
+
+
+}
diff --git a/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java b/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
index e596a91..ff84655 100644
--- a/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
@@ -93,6 +93,7 @@
                             mConnection = (MockConnection) connection;
                             // Modify the connection object created with local values.
                             connection.setConnectionCapabilities(CONNECTION_CAPABILITIES);
+                            connection.setConnectionProperties(CONNECTION_PROPERTIES);
                             connection.setCallerDisplayName(
                                     CALLER_DISPLAY_NAME,
                                     CALLER_DISPLAY_NAME_PRESENTATION);
@@ -273,8 +274,7 @@
 
         assertThat(mCall.getDetails().getCallProperties(), is(Integer.class));
 
-        // No public call properties at the moment, so ensure we have 0 as a return.
-        assertEquals(0, mCall.getDetails().getCallProperties());
+        assertEquals(CALL_PROPERTIES, mCall.getDetails().getCallProperties());
     }
 
     /**
@@ -415,6 +415,214 @@
     }
 
     /**
+     * Tests that {@link Connection} extras changes made via {@link Connection#putExtras(Bundle)}
+     * are propagated to the {@link Call} via
+     * {@link android.telecom.Call.Callback#onDetailsChanged(Call, Call.Details)}.
+     */
+    public void testConnectionPutExtras() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        Bundle testBundle = new Bundle();
+        testBundle.putString(TEST_EXTRA_KEY, TEST_SUBJECT);
+        testBundle.putInt(TEST_EXTRA_KEY2, TEST_EXTRA_VALUE);
+        mConnection.putExtras(testBundle);
+        // Wait for the 2nd invocation; setExtras is called in the setup method.
+        mOnExtrasChangedCounter.waitForCount(2, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+
+        Bundle extras = mCall.getDetails().getExtras();
+        assertEquals(2, extras.size());
+        assertTrue(extras.containsKey(TEST_EXTRA_KEY));
+        assertEquals(TEST_SUBJECT, extras.getString(TEST_EXTRA_KEY));
+        assertTrue(extras.containsKey(TEST_EXTRA_KEY2));
+        assertEquals(TEST_EXTRA_VALUE, extras.getInt(TEST_EXTRA_KEY2));
+    }
+
+    /**
+     * Tests that {@link Connection} extras changes made via {@link Connection#removeExtras(List)}
+     * are propagated to the {@link Call} via
+     * {@link android.telecom.Call.Callback#onDetailsChanged(Call, Call.Details)}.
+     */
+    public void testConnectionRemoveExtras() {
+        testConnectionPutExtras();
+
+        mConnection.removeExtras(Arrays.asList(TEST_EXTRA_KEY));
+        verifyRemoveConnectionExtras();
+
+    }
+
+    /**
+     * Tests that {@link Connection} extras changes made via {@link Connection#removeExtras(List)}
+     * are propagated to the {@link Call} via
+     * {@link android.telecom.Call.Callback#onDetailsChanged(Call, Call.Details)}.
+     */
+    public void testConnectionRemoveExtras2() {
+        testConnectionPutExtras();
+
+        mConnection.removeExtras(TEST_EXTRA_KEY);
+        // testConnectionPutExtra will have waited for the 2nd invocation, so wait for the 3rd here.
+        verifyRemoveConnectionExtras();
+    }
+
+    private void verifyRemoveConnectionExtras() {
+        // testConnectionPutExtra will have waited for the 2nd invocation, so wait for the 3rd here.
+        mOnExtrasChangedCounter.waitForCount(3, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+
+        Bundle extras = mCall.getDetails().getExtras();
+        assertEquals(1, extras.size());
+        assertFalse(extras.containsKey(TEST_EXTRA_KEY));
+        assertTrue(extras.containsKey(TEST_EXTRA_KEY2));
+        assertEquals(TEST_EXTRA_VALUE, extras.getInt(TEST_EXTRA_KEY2));
+    }
+
+    /**
+     * Tests that {@link Call} extras changes made via {@link Call#putExtras(Bundle)} are propagated
+     * to {@link Connection#onExtrasChanged(Bundle)}.
+     */
+    public void testCallPutExtras() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        Bundle testBundle = new Bundle();
+        testBundle.putString(TEST_EXTRA_KEY, TEST_SUBJECT);
+        final InvokeCounter counter = mConnection.getInvokeCounter(
+                MockConnection.ON_EXTRAS_CHANGED);
+        mCall.putExtras(testBundle);
+        counter.waitForCount(1, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+        Bundle extras = mConnection.getExtras();
+
+        assertNotNull(extras);
+        assertTrue(extras.containsKey(TEST_EXTRA_KEY));
+        assertEquals(TEST_SUBJECT, extras.getString(TEST_EXTRA_KEY));
+    }
+
+    /**
+     * Tests that {@link Call} extra operations using {@link Call#removeExtras(List)} are propagated
+     * to the {@link Connection} via {@link Connection#onExtrasChanged(Bundle)}.
+     *
+     * This test specifically tests addition and removal of extras values.
+     */
+    public void testCallRemoveExtras() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        final InvokeCounter counter = setupCallExtras();
+        Bundle extras;
+
+        mCall.removeExtras(Arrays.asList(TEST_EXTRA_KEY));
+        counter.waitForCount(2, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+        extras = mConnection.getExtras();
+        assertNotNull(extras);
+        assertFalse(extras.containsKey(TEST_EXTRA_KEY));
+        assertTrue(extras.containsKey(TEST_EXTRA_KEY2));
+        assertEquals(TEST_EXTRA_VALUE, extras.getInt(TEST_EXTRA_KEY2));
+        assertTrue(extras.containsKey(TEST_EXTRA_KEY3));
+        assertEquals(TEST_SUBJECT, extras.getString(TEST_EXTRA_KEY3));
+
+        mCall.removeExtras(Arrays.asList(TEST_EXTRA_KEY2, TEST_EXTRA_KEY3));
+        counter.waitForCount(3, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+        extras = mConnection.getExtras();
+        assertTrue(extras.isEmpty());
+    }
+
+    /**
+     * Tests that {@link Call} extra operations using {@link Call#removeExtras(String[])} are
+     * propagated to the {@link Connection} via {@link Connection#onExtrasChanged(Bundle)}.
+     *
+     * This test specifically tests addition and removal of extras values.
+     */
+    public void testCallRemoveExtras2() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        final InvokeCounter counter = setupCallExtras();
+        Bundle extras;
+
+        mCall.removeExtras(TEST_EXTRA_KEY);
+        counter.waitForCount(2, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+        extras = mConnection.getExtras();
+        assertNotNull(extras);
+        assertFalse(extras.containsKey(TEST_EXTRA_KEY));
+        assertTrue(extras.containsKey(TEST_EXTRA_KEY2));
+        assertEquals(TEST_EXTRA_VALUE, extras.getInt(TEST_EXTRA_KEY2));
+        assertTrue(extras.containsKey(TEST_EXTRA_KEY3));
+        assertEquals(TEST_SUBJECT, extras.getString(TEST_EXTRA_KEY3));
+    }
+
+    private InvokeCounter setupCallExtras() {
+        Bundle testBundle = new Bundle();
+        testBundle.putString(TEST_EXTRA_KEY, TEST_SUBJECT);
+        testBundle.putInt(TEST_EXTRA_KEY2, TEST_EXTRA_VALUE);
+        testBundle.putString(TEST_EXTRA_KEY3, TEST_SUBJECT);
+        final InvokeCounter counter = mConnection.getInvokeCounter(
+                MockConnection.ON_EXTRAS_CHANGED);
+        mCall.putExtras(testBundle);
+        counter.waitForCount(1, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+        Bundle extras = mConnection.getExtras();
+
+        assertNotNull(extras);
+        assertTrue(extras.containsKey(TEST_EXTRA_KEY));
+        assertEquals(TEST_SUBJECT, extras.getString(TEST_EXTRA_KEY));
+        assertTrue(extras.containsKey(TEST_EXTRA_KEY2));
+        assertEquals(TEST_EXTRA_VALUE, extras.getInt(TEST_EXTRA_KEY2));
+        assertTrue(extras.containsKey(TEST_EXTRA_KEY3));
+        assertEquals(TEST_SUBJECT, extras.getString(TEST_EXTRA_KEY3));
+        return counter;
+    }
+
+    /**
+     * Tests that {@link Connection} events are propagated from
+     * {@link Connection#sendConnectionEvent(String, Bundle)} to
+     * {@link android.telecom.Call.Callback#onConnectionEvent(Call, String, Bundle)}.
+     */
+    public void testConnectionEvent() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        Bundle testBundle = new Bundle();
+        testBundle.putString(TEST_EXTRA_KEY, TEST_SUBJECT);
+
+        mConnection.sendConnectionEvent(Connection.EVENT_CALL_PULL_FAILED, testBundle);
+        mOnConnectionEventCounter.waitForCount(1, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+        String event = (String) (mOnConnectionEventCounter.getArgs(0)[1]);
+        Bundle extras = (Bundle) (mOnConnectionEventCounter.getArgs(0)[2]);
+
+        assertEquals(Connection.EVENT_CALL_PULL_FAILED, event);
+        assertNotNull(extras);
+        assertTrue(extras.containsKey(TEST_EXTRA_KEY));
+        assertEquals(TEST_SUBJECT, extras.getString(TEST_EXTRA_KEY));
+    }
+
+    /**
+     * Tests that {@link Call} events are propagated from {@link Call#sendCallEvent(String, Bundle)}
+     * to {@link Connection#onCallEvent(String, Bundle)}.
+     */
+    public void testCallEvent() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        Bundle testBundle = new Bundle();
+        testBundle.putString(TEST_EXTRA_KEY, TEST_SUBJECT);
+        final InvokeCounter counter = mConnection.getInvokeCounter(MockConnection.ON_CALL_EVENT);
+        mCall.sendCallEvent(TEST_EVENT, testBundle);
+        counter.waitForCount(1, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+
+        String event = (String) (counter.getArgs(0)[0]);
+        Bundle extras = (Bundle) (counter.getArgs(0)[1]);
+
+        assertEquals(TEST_EVENT, event);
+        assertNotNull(extras);
+        assertTrue(extras.containsKey(TEST_EXTRA_KEY));
+        assertEquals(TEST_SUBJECT, extras.getString(TEST_EXTRA_KEY));
+    }
+
+    /**
      * Asserts that a call's extras contain a specified key.
      *
      * @param call The call.
diff --git a/tests/tests/telecom/src/android/telecom/cts/CallTest.java b/tests/tests/telecom/src/android/telecom/cts/CallTest.java
index d63a9e8..b892ede 100644
--- a/tests/tests/telecom/src/android/telecom/cts/CallTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/CallTest.java
@@ -32,6 +32,8 @@
                 | CAPABILITY_DISCONNECT_FROM_CONFERENCE | CAPABILITY_MUTE,
                 CAPABILITY_MUTE));
         assertTrue(Call.Details.can(CAPABILITY_CAN_PAUSE_VIDEO, CAPABILITY_CAN_PAUSE_VIDEO));
+        assertTrue(Call.Details.can(CAPABILITY_CAN_PULL_CALL, CAPABILITY_CAN_PULL_CALL));
+
         assertFalse(Call.Details.can(CAPABILITY_MUTE, CAPABILITY_HOLD));
         assertFalse(Call.Details.can(CAPABILITY_MERGE_CONFERENCE
                 | CAPABILITY_DISCONNECT_FROM_CONFERENCE | CAPABILITY_MUTE,
@@ -78,6 +80,8 @@
         assertTrue(Call.Details.hasProperty(PROPERTY_HIGH_DEF_AUDIO, PROPERTY_HIGH_DEF_AUDIO));
         assertTrue(Call.Details.hasProperty(PROPERTY_HIGH_DEF_AUDIO | PROPERTY_CONFERENCE
                 | PROPERTY_WIFI, PROPERTY_CONFERENCE));
+        assertTrue(Call.Details.hasProperty(PROPERTY_IS_EXTERNAL_CALL, PROPERTY_IS_EXTERNAL_CALL));
+
         assertFalse(Call.Details.hasProperty(PROPERTY_WIFI, PROPERTY_CONFERENCE));
         assertFalse(Call.Details.hasProperty(PROPERTY_HIGH_DEF_AUDIO | PROPERTY_CONFERENCE
                 | PROPERTY_WIFI, PROPERTY_GENERIC_CONFERENCE));
diff --git a/tests/tests/telecom/src/android/telecom/cts/ConferenceTest.java b/tests/tests/telecom/src/android/telecom/cts/ConferenceTest.java
index 913ca82..0967fda 100644
--- a/tests/tests/telecom/src/android/telecom/cts/ConferenceTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/ConferenceTest.java
@@ -222,6 +222,118 @@
         assertCallState(conf, Call.STATE_DISCONNECTED);
     }
 
+    /**
+     * Tests end to end propagation of the {@link Conference} properties to the associated
+     * {@link Call}.
+     */
+    public void testConferenceProperties() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+        final Call conf = mInCallService.getLastConferenceCall();
+        assertCallState(conf, Call.STATE_ACTIVE);
+
+        int properties  = mConferenceObject.getConnectionProperties();
+        properties |= Connection.PROPERTY_HAS_CDMA_VOICE_PRIVACY;
+
+        mConferenceObject.setConnectionProperties(properties);
+
+        // Wait for 2nd properties change; the first will be when the conference is marked with
+        // Call.Details.PROPERTY_CONFERENCE.
+        assertCallProperties(conf, Call.Details.PROPERTY_HAS_CDMA_VOICE_PRIVACY);
+        assertTrue(conf.getDetails().hasProperty(Call.Details.PROPERTY_HAS_CDMA_VOICE_PRIVACY));
+    }
+
+    /**
+     * Verifies {@link Conference#putExtras(Bundle)} calls are propagated to
+     * {@link android.telecom.Call.Callback#onDetailsChanged(Call, Call.Details)}.
+     */
+    public void testConferencePutExtras() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+        final Call conf = mInCallService.getLastConferenceCall();
+        assertCallState(conf, Call.STATE_ACTIVE);
+
+        Bundle extras = new Bundle();
+        extras.putString(TEST_EXTRA_KEY_1, TEST_EXTRA_VALUE_1);
+        extras.putInt(TEST_EXTRA_KEY_2, TEST_EXTRA_VALUE_2);
+        mConferenceObject.putExtras(extras);
+
+        mOnExtrasChangedCounter.waitForCount(1);
+
+        assertTrue(areBundlesEqual(extras, conf.getDetails().getExtras()));
+    }
+
+    /**
+     * Verifies {@link Conference#removeExtras(List)} calls are propagated to
+     * {@link android.telecom.Call.Callback#onDetailsChanged(Call, Call.Details)}.
+     */
+    public void testConferenceRemoveExtras() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+        final Call conf = mInCallService.getLastConferenceCall();
+        assertCallState(conf, Call.STATE_ACTIVE);
+
+        setupExtras();
+
+        mConferenceObject.removeExtras(Arrays.asList(TEST_EXTRA_KEY_1));
+        mOnExtrasChangedCounter.waitForCount(2);
+        Bundle extras = mConferenceObject.getExtras();
+
+        assertFalse(extras.containsKey(TEST_EXTRA_KEY_1));
+        assertTrue(extras.containsKey(TEST_EXTRA_KEY_2));
+    }
+
+    /**
+     * Verifies {@link Conference#removeExtras(String[])} calls are propagated to
+     * {@link android.telecom.Call.Callback#onDetailsChanged(Call, Call.Details)}.
+     */
+    public void testConferenceRemoveExtras2() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+        final Call conf = mInCallService.getLastConferenceCall();
+        assertCallState(conf, Call.STATE_ACTIVE);
+
+        setupExtras();
+
+        mConferenceObject.removeExtras(TEST_EXTRA_KEY_1, TEST_EXTRA_KEY_2);
+        mOnExtrasChangedCounter.waitForCount(2);
+        Bundle extras = mConferenceObject.getExtras();
+
+        assertNull(extras);
+    }
+
+    private void setupExtras() {
+        Bundle extras = new Bundle();
+        extras.putString(TEST_EXTRA_KEY_1, TEST_EXTRA_VALUE_1);
+        extras.putInt(TEST_EXTRA_KEY_2, TEST_EXTRA_VALUE_2);
+        mConferenceObject.putExtras(extras);
+        mOnExtrasChangedCounter.waitForCount(1);
+    }
+
+    /**
+     * Verifies {@link android.telecom.Call#putExtras(Bundle)} changes are propagated to
+     * {@link Conference#onExtrasChanged(Bundle)}.
+     */
+    public void testConferenceOnExtraschanged() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+        final Call conf = mInCallService.getLastConferenceCall();
+        assertCallState(conf, Call.STATE_ACTIVE);
+
+        Bundle extras = new Bundle();
+        extras.putString(TEST_EXTRA_KEY_1, TEST_EXTRA_VALUE_1);
+        extras.putInt(TEST_EXTRA_KEY_2, TEST_EXTRA_VALUE_2);
+        conf.putExtras(extras);
+        mConferenceObject.mOnExtrasChanged.waitForCount(1);
+
+        assertTrue(areBundlesEqual(extras, mConferenceObject.getExtras()));
+    }
+
     public void testConferenceAddAndRemoveConnection() {
         if (!mShouldTestTelecom) {
             return;
diff --git a/tests/tests/telecom/src/android/telecom/cts/ConnectionServiceTest.java b/tests/tests/telecom/src/android/telecom/cts/ConnectionServiceTest.java
index 21b00a0..deab32f 100644
--- a/tests/tests/telecom/src/android/telecom/cts/ConnectionServiceTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/ConnectionServiceTest.java
@@ -18,9 +18,11 @@
 
 import static android.telecom.cts.TestUtils.*;
 
+import android.content.ComponentName;
 import android.telecom.Call;
 import android.telecom.Connection;
 import android.telecom.ConnectionService;
+import android.telecom.PhoneAccountHandle;
 import android.util.Log;
 
 import java.util.Collection;
@@ -58,6 +60,52 @@
         assertCallState(call, Call.STATE_HOLDING);
     }
 
+    public void testAddExistingConnection_invalidPhoneAccountPackageName() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        placeAndVerifyCall();
+        verifyConnectionForOutgoingCall();
+
+        // Add second connection (add existing connection)
+        final MockConnection connection = new MockConnection();
+        connection.setOnHold();
+        ComponentName invalidName = new ComponentName("com.android.phone",
+                "com.android.services.telephony.TelephonyConnectionService");
+        // This command will fail and a SecurityException will be thrown by Telecom. The Exception
+        // will then be absorbed by the ConnectionServiceAdapter.
+        CtsConnectionService.addExistingConnectionToTelecom(new PhoneAccountHandle(invalidName,
+                "Test"), connection);
+        // Make sure that only the original Call exists.
+        assertNumCalls(mInCallCallbacks.getService(), 1);
+        mInCallCallbacks.lock.drainPermits();
+        final Call call = mInCallCallbacks.getService().getLastCall();
+        assertCallState(call, Call.STATE_DIALING);
+    }
+
+    public void testAddExistingConnection_invalidPhoneAccountAccountId() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        placeAndVerifyCall();
+        verifyConnectionForOutgoingCall();
+
+        // Add second connection (add existing connection)
+        final MockConnection connection = new MockConnection();
+        connection.setOnHold();
+        ComponentName validName = new ComponentName(PACKAGE, COMPONENT);
+        // This command will fail because the PhoneAccount is not registered to Telecom currently.
+        CtsConnectionService.addExistingConnectionToTelecom(new PhoneAccountHandle(validName,
+                "Invalid Account Id"), connection);
+        // Make sure that only the original Call exists.
+        assertNumCalls(mInCallCallbacks.getService(), 1);
+        mInCallCallbacks.lock.drainPermits();
+        final Call call = mInCallCallbacks.getService().getLastCall();
+        assertCallState(call, Call.STATE_DIALING);
+    }
+
     public void testGetAllConnections() {
         if (!mShouldTestTelecom) {
             return;
diff --git a/tests/tests/telecom/src/android/telecom/cts/ConnectionTest.java b/tests/tests/telecom/src/android/telecom/cts/ConnectionTest.java
index 58dfcdb..874f116 100644
--- a/tests/tests/telecom/src/android/telecom/cts/ConnectionTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/ConnectionTest.java
@@ -164,6 +164,22 @@
         assertEquals(capabilities, connection.getConnectionCapabilities());
     }
 
+    public void testSetAndGetConnectionProperties() {
+        if (!shouldTestTelecom(getContext())) {
+            return;
+        }
+
+        final Semaphore lock = new Semaphore(0);
+        Connection connection = createConnection(lock);
+        waitForStateChange(lock);
+
+        final int properties = Connection.PROPERTY_IS_EXTERNAL_CALL;
+
+        connection.setConnectionProperties(properties);
+
+        assertEquals(properties, connection.getConnectionProperties());
+    }
+
     public void testSetAndGetDisconnectCause() {
         if (!shouldTestTelecom(getContext())) {
             return;
@@ -217,6 +233,79 @@
         assertTrue(extras.getBoolean("test-extra-key"));
     }
 
+    /**
+     * Basic local test of adding extra keys via {@link Connection#removeExtras(List)}.
+     *
+     * Extended end-to-end passing of extras is verified in
+     * {@link CallDetailsTest#testConnectionPutExtras()} and
+     * @link CallDetailsTest#testConnectionRemoveExtras()}.
+     */
+    public void testPutExtras() {
+        if (!shouldTestTelecom(getContext())) {
+            return;
+        }
+
+        final Semaphore lock = new Semaphore(0);
+        Connection connection = createConnection(lock);
+        waitForStateChange(lock);
+
+        assertEquals(null, connection.getExtras());
+
+        final Bundle extras = new Bundle();
+        extras.putBoolean("test-extra-key", true);
+        connection.putExtras(extras);
+
+        final Bundle retrieved = connection.getExtras();
+        assertNotNull(retrieved);
+        assertTrue(extras.getBoolean("test-extra-key"));
+    }
+
+    /**
+     * Basic local test of removing extra keys via {@link Connection#removeExtras(List)}.
+     *
+     * Extended end-to-end passing of extras is verified in
+     * {@link CallDetailsTest#testConnectionPutExtras()} and
+     * @link CallDetailsTest#testConnectionRemoveExtras()}.
+     */
+    public void testRemoveExtras() {
+        if (!shouldTestTelecom(getContext())) {
+            return;
+        }
+
+        final Semaphore lock = new Semaphore(0);
+        Connection connection = createConnection(lock);
+        waitForStateChange(lock);
+
+        assertEquals(null, connection.getExtras());
+
+        final Bundle extras = new Bundle();
+        extras.putBoolean("test-extra-key", true);
+        connection.putExtras(extras);
+        connection.removeExtras(Arrays.asList("test-extra-key"));
+
+        final Bundle retrieved = connection.getExtras();
+        assertNotNull(retrieved);
+        assertFalse(extras.containsKey("test-extra-key"));
+    }
+
+    /**
+     * Tests that the {@link Connection#sendConnectionEvent(String, Bundle)} method exists and can
+     * be called.
+     *
+     * Actual end-to-end tests can be found in {@link CallDetailsTest#testConnectionEvent()}.
+     */
+    public void testSendConnectionEvent() {
+        if (!shouldTestTelecom(getContext())) {
+            return;
+        }
+
+        final Semaphore lock = new Semaphore(0);
+        Connection connection = createConnection(lock);
+        waitForStateChange(lock);
+
+        connection.sendConnectionEvent("test", null);
+    }
+
     public void testSetAndGetStatusHints() {
         if (!shouldTestTelecom(getContext())) {
             return;
@@ -295,6 +384,18 @@
                         | Connection.CAPABILITY_MANAGE_CONFERENCE));
     }
 
+    /**
+     * Tests the {@link Connection#propertiesToString(int)} method.
+     */
+    public void testPropertiesToString() {
+        if (!shouldTestTelecom(getContext())) {
+            return;
+        }
+
+        assertEquals("[Properties: PROPERTY_IS_EXTERNAL_CALL]",
+                Connection.propertiesToString(Connection.PROPERTY_IS_EXTERNAL_CALL));
+    }
+
     private static Connection createConnection(final Semaphore lock) {
         BasicConnection connection = new BasicConnection();
         connection.setLock(lock);
diff --git a/tests/tests/telecom/src/android/telecom/cts/ExternalCallTest.java b/tests/tests/telecom/src/android/telecom/cts/ExternalCallTest.java
new file mode 100644
index 0000000..b50e5cc
--- /dev/null
+++ b/tests/tests/telecom/src/android/telecom/cts/ExternalCallTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.telecom.cts;
+
+import android.telecom.Call;
+import android.telecom.Connection;
+import android.telecom.ConnectionRequest;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+
+import static android.telecom.cts.TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS;
+
+/**
+ * Tests which verify functionality related to {@link android.telecom.Connection}s and
+ * {@link android.telecom.Call}s with the
+ * {@link android.telecom.Connection#PROPERTY_IS_EXTERNAL_CALL} and
+ * {@link android.telecom.Call.Details#PROPERTY_IS_EXTERNAL_CALL} properties, respectively, set.
+ */
+public class ExternalCallTest extends BaseTelecomTestWithMockServices {
+    public static final int CONNECTION_PROPERTIES = Connection.PROPERTY_IS_EXTERNAL_CALL;
+    public static final int CONNECTION_CAPABILITIES = Connection.CAPABILITY_CAN_PULL_CALL;
+
+    private Call mCall;
+    private MockConnection mConnection;
+    private MockInCallService mInCallService;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        if (mShouldTestTelecom) {
+            PhoneAccount account = setupConnectionService(
+                    new MockConnectionService() {
+                        @Override
+                        public Connection onCreateOutgoingConnection(
+                                PhoneAccountHandle connectionManagerPhoneAccount,
+                                ConnectionRequest request) {
+                            Connection connection = super.onCreateOutgoingConnection(
+                                    connectionManagerPhoneAccount,
+                                    request);
+                            mConnection = (MockConnection) connection;
+                            // Modify the connection object created with local values.
+                            connection.setConnectionCapabilities(CONNECTION_CAPABILITIES);
+                            connection.setConnectionProperties(CONNECTION_PROPERTIES);
+
+                            lock.release();
+                            return connection;
+                        }
+                    }, FLAG_REGISTER | FLAG_ENABLE);
+
+            placeAndVerifyCall();
+            verifyConnectionForOutgoingCall();
+
+            mInCallService = mInCallCallbacks.getService();
+            mCall = mInCallService.getLastCall();
+
+            assertCallState(mCall, Call.STATE_DIALING);
+            assertCallProperties(mCall, Call.Details.PROPERTY_IS_EXTERNAL_CALL);
+            assertCallCapabilities(mCall, Call.Details.CAPABILITY_CAN_PULL_CALL);
+        }
+    }
+
+    /**
+     * Tests that a request to pull an external call via {@link Call#pullExternalCall()} is
+     * communicated to the {@link Connection} via {@link Connection#onPullExternalCall()}.
+     */
+    public void testPullExternalCall() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        final InvokeCounter counter = mConnection.getInvokeCounter(
+                MockConnection.ON_PULL_EXTERNAL_CALL);
+        mCall.pullExternalCall();
+        counter.waitForCount(1, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+    }
+
+    public void testNonPullableExternalCall() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        // Remove the pullable attribute of the connection.
+        mConnection.setConnectionCapabilities(0);
+        assertCallCapabilities(mCall, 0);
+
+        final InvokeCounter counter = mConnection.getInvokeCounter(
+                MockConnection.ON_PULL_EXTERNAL_CALL);
+        // Try to pull -- we expect Telecom to absorb the request since the call is not pullable.
+        mCall.pullExternalCall();
+        counter.waitForCount(0, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+    }
+}
diff --git a/tests/tests/telecom/src/android/telecom/cts/MockConference.java b/tests/tests/telecom/src/android/telecom/cts/MockConference.java
index 89c3772..d84610d 100644
--- a/tests/tests/telecom/src/android/telecom/cts/MockConference.java
+++ b/tests/tests/telecom/src/android/telecom/cts/MockConference.java
@@ -152,4 +152,9 @@
     public String getDtmfString() {
         return mDtmfString;
     }
+
+    @Override
+    public void onExtrasChanged(Bundle extras) {
+        mOnExtrasChanged.invoke(extras);
+    }
 }
diff --git a/tests/tests/telecom/src/android/telecom/cts/MockConnection.java b/tests/tests/telecom/src/android/telecom/cts/MockConnection.java
index 708540a..4436219 100644
--- a/tests/tests/telecom/src/android/telecom/cts/MockConnection.java
+++ b/tests/tests/telecom/src/android/telecom/cts/MockConnection.java
@@ -163,6 +163,30 @@
         }
     }
 
+    @Override
+    public void onCallEvent(String event, Bundle extras) {
+        super.onCallEvent(event, extras);
+        if (mInvokeCounterMap.get(ON_CALL_EVENT) != null) {
+            mInvokeCounterMap.get(ON_CALL_EVENT).invoke(event, extras);
+        }
+    }
+
+    @Override
+    public void onPullExternalCall() {
+        super.onPullExternalCall();
+        if (mInvokeCounterMap.get(ON_PULL_EXTERNAL_CALL) != null) {
+            mInvokeCounterMap.get(ON_PULL_EXTERNAL_CALL).invoke();
+        }
+    }
+
+    @Override
+    public void onExtrasChanged(Bundle extras) {
+        super.onExtrasChanged(extras);
+        if (mInvokeCounterMap.get(ON_EXTRAS_CHANGED) != null) {
+            mInvokeCounterMap.get(ON_EXTRAS_CHANGED).invoke(extras);
+        }
+    }
+
     public int getCurrentState()  {
         return mState;
     }
diff --git a/tests/tests/telecom/src/android/telecom/cts/MockInCallService.java b/tests/tests/telecom/src/android/telecom/cts/MockInCallService.java
index d59a801..4ff3cb6 100644
--- a/tests/tests/telecom/src/android/telecom/cts/MockInCallService.java
+++ b/tests/tests/telecom/src/android/telecom/cts/MockInCallService.java
@@ -145,6 +145,14 @@
                 getCallbacks().onCannedTextResponsesLoaded(call, cannedTextResponses);
             }
         }
+
+        @Override
+        public void onConnectionEvent(Call call, String event, Bundle extras) {
+            super.onConnectionEvent(call, event, extras);
+            if (getCallbacks() != null) {
+                getCallbacks().onConnectionEvent(call, event, extras);
+            }
+        }
     };
 
     private void saveVideoCall(Call call, VideoCall videoCall) {
diff --git a/tests/tests/telecom/src/android/telecom/cts/RemoteConferenceTest.java b/tests/tests/telecom/src/android/telecom/cts/RemoteConferenceTest.java
index f29e09d..3246b9c 100644
--- a/tests/tests/telecom/src/android/telecom/cts/RemoteConferenceTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/RemoteConferenceTest.java
@@ -343,6 +343,35 @@
         mRemoteConferenceObject.unregisterCallback(callback);
     }
 
+    public void testRemoteConferenceCallbacks_ConnectionProperties() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+        Handler handler = setupRemoteConferenceCallbacksTest();
+
+        final InvokeCounter callbackInvoker =
+                new InvokeCounter("testRemoteConferenceCallbacks_ConnectionProperties");
+        RemoteConference.Callback callback;
+
+        callback = new RemoteConference.Callback() {
+            @Override
+            public void onConnectionPropertiesChanged(
+                    RemoteConference conference,
+                    int connectionProperties) {
+                super.onConnectionPropertiesChanged(conference, connectionProperties);
+                callbackInvoker.invoke(conference, connectionProperties);
+            }
+        };
+        mRemoteConferenceObject.registerCallback(callback, handler);
+        int properties = mRemoteConference.getConnectionCapabilities()
+                | Connection.PROPERTY_IS_EXTERNAL_CALL;
+        mRemoteConference.setConnectionProperties(properties);
+        callbackInvoker.waitForCount(1, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+        assertEquals(mRemoteConferenceObject, callbackInvoker.getArgs(0)[0]);
+        assertEquals(properties, callbackInvoker.getArgs(0)[1]);
+        mRemoteConferenceObject.unregisterCallback(callback);
+    }
+
     public void testRemoteConferenceCallbacks_ConferenceableConnections() {
         if (!mShouldTestTelecom) {
             return;
diff --git a/tests/tests/telecom/src/android/telecom/cts/RemoteConnectionTest.java b/tests/tests/telecom/src/android/telecom/cts/RemoteConnectionTest.java
index eb9e055..81080b0 100644
--- a/tests/tests/telecom/src/android/telecom/cts/RemoteConnectionTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/RemoteConnectionTest.java
@@ -240,6 +240,36 @@
         mRemoteConnectionObject.unregisterCallback(callback);
     }
 
+    public void testRemoteConnectionCallbacks_ConnectionProperties() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        Handler handler = setupRemoteConnectionCallbacksTest();
+
+        final InvokeCounter callbackInvoker =
+                new InvokeCounter("testRemoteConnectionCallbacks_ConnectionCapabilities");
+        RemoteConnection.Callback callback;
+
+        callback = new RemoteConnection.Callback() {
+            @Override
+            public void onConnectionPropertiesChanged(
+                    RemoteConnection connection,
+                    int connectionProperties) {
+                super.onConnectionPropertiesChanged(connection, connectionProperties);
+                callbackInvoker.invoke(connection, connectionProperties);
+            }
+        };
+        mRemoteConnectionObject.registerCallback(callback, handler);
+        int properties = mRemoteConnection.getConnectionCapabilities()
+                | Connection.PROPERTY_IS_EXTERNAL_CALL;
+        mRemoteConnection.setConnectionProperties(properties);
+        callbackInvoker.waitForCount(1, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+        assertEquals(mRemoteConnectionObject, callbackInvoker.getArgs(0)[0]);
+        assertEquals(properties, callbackInvoker.getArgs(0)[1]);
+        mRemoteConnectionObject.unregisterCallback(callback);
+    }
+
     public void testRemoteConnectionCallbacks_PostDialWait() {
         if (!mShouldTestTelecom) {
             return;
@@ -528,6 +558,40 @@
         mRemoteConnectionObject.unregisterCallback(callback);
     }
 
+    /**
+     * Verifies that a {@link RemoteConnection} receives a
+     * {@link Connection#sendConnectionEvent(String, Bundle)} notification.
+     */
+    public void testRemoteConnectionCallbacks_ConnectionEvent() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        Handler handler = setupRemoteConnectionCallbacksTest();
+
+        final InvokeCounter callbackInvoker =
+                new InvokeCounter("testRemoteConnectionCallbacks_Extras");
+        RemoteConnection.Callback callback;
+
+        callback = new RemoteConnection.Callback() {
+            @Override
+            public void onConnectionEvent(RemoteConnection connection, String event,
+                    Bundle extras) {
+                super.onConnectionEvent(connection, event, extras);
+                callbackInvoker.invoke(connection, event, extras);
+            }
+        };
+        mRemoteConnectionObject.registerCallback(callback, handler);
+        Bundle extras = new Bundle();
+        extras.putString(TelecomManager.EXTRA_CALL_DISCONNECT_MESSAGE, "Test");
+        mRemoteConnection.sendConnectionEvent(Connection.EVENT_CALL_PULL_FAILED, extras);
+        callbackInvoker.waitForCount(1, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+        assertEquals(mRemoteConnectionObject, callbackInvoker.getArgs(0)[0]);
+        assertEquals(Connection.EVENT_CALL_PULL_FAILED, callbackInvoker.getArgs(0)[1]);
+        assertTrue(areBundlesEqual(extras, (Bundle) callbackInvoker.getArgs(0)[2]));
+        mRemoteConnectionObject.unregisterCallback(callback);
+    }
+
     public void testRemoteConnectionCallbacks_Disconnect() {
         if (!mShouldTestTelecom) {
             return;
@@ -557,6 +621,23 @@
         mRemoteConnectionObject.unregisterCallback(callback);
     }
 
+    /**
+     * Verifies that a call to {@link RemoteConnection#pullExternalCall()} is proxied to
+     * {@link Connection#onPullExternalCall()}.
+     */
+    public void testRemoteConnectionCallbacks_PullExternalCall() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        Handler handler = setupRemoteConnectionCallbacksTest();
+
+        InvokeCounter counter =
+                mRemoteConnection.getInvokeCounter(MockConnection.ON_PULL_EXTERNAL_CALL);
+        mRemoteConnectionObject.pullExternalCall();
+        counter.waitForCount(1);
+    }
+
     public void testRemoteConnectionCallbacks_Destroy() {
         if (!mShouldTestTelecom) {
             return;
@@ -1104,6 +1185,8 @@
                 remoteConnection.getCallerDisplayNamePresentation());
         assertEquals(connection.getConnectionCapabilities(),
                 remoteConnection.getConnectionCapabilities());
+        assertEquals(connection.getConnectionProperties(),
+                remoteConnection.getConnectionProperties());
         assertEquals(connection.getDisconnectCause(), remoteConnection.getDisconnectCause());
         assertEquals(connection.getExtras(), remoteConnection.getExtras());
         assertEquals(connection.getStatusHints(), remoteConnection.getStatusHints());
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ExactCanvasTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ExactCanvasTests.java
index 108adf0..6f90433 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ExactCanvasTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ExactCanvasTests.java
@@ -182,4 +182,27 @@
                 .runWithComparer(mExactComparer);
     }
 
+    @Test
+    public void testSaveLayerRounding() {
+        createTest()
+                .addCanvasClient((canvas, width, height) -> {
+                    canvas.saveLayerAlpha(10.5f, 10.5f, 79.5f, 79.5f, 255);
+                    canvas.drawRect(20, 20, 70, 70, new Paint());
+                    canvas.restore();
+                })
+                .runWithVerifier(new RectVerifier(Color.WHITE, Color.BLACK,
+                        new Rect(20, 20, 70, 70)));
+    }
+
+    @Test
+    public void testUnclippedSaveLayerRounding() {
+        createTest()
+                .addCanvasClient((canvas, width, height) -> {
+                    canvas.saveLayerAlpha(10.5f, 10.5f, 79.5f, 79.5f, 255, 0);
+                    canvas.drawRect(20, 20, 70, 70, new Paint());
+                    canvas.restore();
+                })
+                .runWithVerifier(new RectVerifier(Color.WHITE, Color.BLACK,
+                        new Rect(20, 20, 70, 70)));
+    }
 }
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java
index 53112d6..4f99378 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java
@@ -28,8 +28,11 @@
 import android.uirendering.cts.bitmapverifiers.ColorVerifier;
 import android.uirendering.cts.testinfrastructure.ActivityTestBase;
 import android.uirendering.cts.testinfrastructure.ViewInitializer;
+import android.view.Gravity;
 import android.view.View;
+import android.view.ViewTreeObserver;
 import android.uirendering.cts.R;
+import android.widget.FrameLayout;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -93,4 +96,63 @@
                 })
                 .runWithVerifier(new ColorVerifier(expectedColor));
     }
+
+    @Test
+    public void testLayerInitialSizeZero() {
+        createTest()
+                .addLayout(R.layout.frame_layout, view -> {
+                    FrameLayout root = (FrameLayout) view.findViewById(R.id.frame_layout);
+                    // disable clipChildren, to ensure children aren't rejected by bounds
+                    root.setClipChildren(false);
+                    for (int i = 0; i < 2; i++) {
+                        View child = new View(view.getContext());
+                        child.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+                        // add rendering content, so View isn't skipped at render time
+                        child.setBackgroundColor(Color.RED);
+
+                        // add one with width=0, one with height=0
+                        root.addView(child, new FrameLayout.LayoutParams(
+                                i == 0 ? 0 : 90,
+                                i == 0 ? 90 : 0,
+                                Gravity.TOP | Gravity.LEFT));
+                    }
+                }, true)
+                .runWithVerifier(new ColorVerifier(Color.WHITE, 0 /* zero tolerance */));
+    }
+
+    @Test
+    public void testLayerResizeZero() {
+        createTest()
+                .addLayout(R.layout.frame_layout, view -> {
+                    FrameLayout root = (FrameLayout) view.findViewById(R.id.frame_layout);
+                    // disable clipChildren, to ensure child isn't rejected by bounds
+                    root.setClipChildren(false);
+                    for (int i = 0; i < 2; i++) {
+                        View child = new View(view.getContext());
+                        child.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+                        // add rendering content, so View isn't skipped at render time
+                        child.setBackgroundColor(Color.BLUE);
+                        root.addView(child, new FrameLayout.LayoutParams(90, 90,
+                                Gravity.TOP | Gravity.LEFT));
+                    }
+
+                    // post invalid dimensions a few frames in, so initial layer allocation succeeds
+                    // NOTE: this must execute before capture, or verification will fail
+                    root.getViewTreeObserver().addOnPreDrawListener(
+                            new ViewTreeObserver.OnPreDrawListener() {
+                        int mDrawCount = 0;
+                        @Override
+                        public boolean onPreDraw() {
+                            if (mDrawCount++ == 5) {
+                                root.getChildAt(0).getLayoutParams().width = 0;
+                                root.getChildAt(0).requestLayout();
+                                root.getChildAt(1).getLayoutParams().height = 0;
+                                root.getChildAt(1).requestLayout();
+                            }
+                            return true;
+                        }
+                    });
+                }, true)
+                .runWithVerifier(new ColorVerifier(Color.WHITE, 0 /* zero tolerance */));
+    }
 }
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/PathClippingTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/PathClippingTests.java
index 7c8a301..a3145ef 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/PathClippingTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/PathClippingTests.java
@@ -17,7 +17,6 @@
 package android.uirendering.cts.testclasses;
 
 import android.content.pm.PackageManager;
-import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.Path;
@@ -28,6 +27,7 @@
 import android.uirendering.cts.bitmapverifiers.SamplePointVerifier;
 import android.uirendering.cts.testinfrastructure.ActivityTestBase;
 import android.uirendering.cts.testinfrastructure.CanvasClient;
+import android.uirendering.cts.testinfrastructure.CanvasClientDrawable;
 import android.uirendering.cts.testinfrastructure.ViewInitializer;
 import android.view.View;
 import android.view.ViewGroup;
@@ -96,7 +96,7 @@
     @Test
     public void testViewRotate() {
         createTest()
-                .addLayout(R.layout.blue_padded_layout, (ViewInitializer) view -> {
+                .addLayout(R.layout.blue_padded_layout, view -> {
                     ViewGroup rootView = (ViewGroup) view;
                     rootView.setClipChildren(true);
                     View childView = rootView.getChildAt(0);
@@ -122,6 +122,23 @@
     }
 
     @Test
+    public void testPathScale() {
+        createTest()
+                .addLayout(R.layout.frame_layout, view -> {
+                    Path path = new Path();
+                    path.addCircle(TEST_WIDTH / 2, TEST_HEIGHT / 2,
+                            TEST_WIDTH / 4, Path.Direction.CW);
+                    view.setBackground(new CanvasClientDrawable((canvas, width, height) -> {
+                        canvas.clipPath(path);
+                        canvas.drawColor(Color.BLUE);
+                    }));
+                    view.setScaleX(2);
+                    view.setScaleY(2);
+                })
+                .runWithComparer(new MSSIMComparer(0.90));
+    }
+
+    @Test
     public void testTextClip() {
         createTest()
                 .addCanvasClient((canvas, width, height) -> {
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/CanvasClient.java b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/CanvasClient.java
index 99f6cc7..13db944 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/CanvasClient.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/CanvasClient.java
@@ -18,7 +18,10 @@
 import android.graphics.Canvas;
 
 /**
- * A class that the tester will implement and create a set of drawing calls the tests would use
+ * An interface for specifying canvas commands.
+ *
+ * Implementations of the interface are not required to save/restore canvas state -
+ * callers of draw() will handle saving/restoring as necessary.
  */
 public interface CanvasClient {
     void draw(Canvas canvas, int width, int height);
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/CanvasClientDrawable.java b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/CanvasClientDrawable.java
new file mode 100644
index 0000000..d796e6c
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/CanvasClientDrawable.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 android.uirendering.cts.testinfrastructure;
+
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.drawable.Drawable;
+
+public class CanvasClientDrawable extends Drawable {
+    private final CanvasClient mCanvasClient;
+
+    public CanvasClientDrawable(CanvasClient canvasClient) {
+        mCanvasClient = canvasClient;
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        int saveCount = canvas.save();
+        canvas.clipRect(0, 0, ActivityTestBase.TEST_WIDTH, ActivityTestBase.TEST_HEIGHT);
+        mCanvasClient.draw(canvas, ActivityTestBase.TEST_WIDTH, ActivityTestBase.TEST_HEIGHT);
+        canvas.restoreToCount(saveCount);
+    }
+
+    @Override
+    public void setAlpha(int alpha) {}
+
+    @Override
+    public void setColorFilter(ColorFilter colorFilter) {}
+
+    @Override
+    public int getOpacity() {
+        return 0;
+    }
+}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/CanvasClientView.java b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/CanvasClientView.java
index 60127ae..81183e5 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/CanvasClientView.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/CanvasClientView.java
@@ -55,9 +55,9 @@
         }
         if (mCanvasClient == null) throw new IllegalStateException("Canvas client missing");
 
-        canvas.save();
+        int saveCount = canvas.save();
         canvas.clipRect(0, 0, ActivityTestBase.TEST_WIDTH, ActivityTestBase.TEST_HEIGHT);
         mCanvasClient.draw(canvas, ActivityTestBase.TEST_WIDTH, ActivityTestBase.TEST_HEIGHT);
-        canvas.restore();
+        canvas.restoreToCount(saveCount);
     }
 }
diff --git a/tests/tests/view/Android.mk b/tests/tests/view/Android.mk
index 6a8d49c..ba4be93 100644
--- a/tests/tests/view/Android.mk
+++ b/tests/tests/view/Android.mk
@@ -29,11 +29,15 @@
 LOCAL_JAVA_LIBRARIES := android.test.runner
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
-    ctsdeviceutil ctstestrunner mockito-target
+    ctsdeviceutil \
+    ctstestrunner \
+    mockito-target \
+    ub-uiautomator \
+    android-support-test
 
 LOCAL_JNI_SHARED_LIBRARIES := libctsview_jni libnativehelper_compat_libc++
 
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-renderscript-files-under, src)
 
 LOCAL_PACKAGE_NAME := CtsViewTestCases
 
diff --git a/tests/tests/view/AndroidManifest.xml b/tests/tests/view/AndroidManifest.xml
index a98c447..ba1f3d2 100644
--- a/tests/tests/view/AndroidManifest.xml
+++ b/tests/tests/view/AndroidManifest.xml
@@ -239,6 +239,13 @@
             </intent-filter>
         </activity>
 
+        <activity android:name="android.view.cts.surfacevalidator.CapturedActivity"
+                  android:theme="@style/WhiteBackgroundTheme">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
     </application>
 
     <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/view/res/raw/colors_video.mp4 b/tests/tests/view/res/raw/colors_video.mp4
new file mode 100644
index 0000000..0bec670
--- /dev/null
+++ b/tests/tests/view/res/raw/colors_video.mp4
Binary files differ
diff --git a/tests/tests/view/res/values/styles.xml b/tests/tests/view/res/values/styles.xml
index 9de4abd..4979241 100644
--- a/tests/tests/view/res/values/styles.xml
+++ b/tests/tests/view/res/values/styles.xml
@@ -177,4 +177,13 @@
         <item name="android:windowSwipeToDismiss">false</item>
     </style>
 
+    <style name="WhiteBackgroundTheme" parent="@android:style/Theme.Holo.NoActionBar.Fullscreen">
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowFullscreen">true</item>
+        <item name="android:windowOverscan">true</item>
+        <item name="android:fadingEdge">none</item>
+        <item name="android:windowBackground">@android:color/white</item>
+        <item name="android:windowContentTransitions">false</item>
+        <item name="android:windowAnimationStyle">@null</item>
+    </style>
 </resources>
diff --git a/tests/tests/view/src/android/view/cts/SurfaceViewSyncTests.java b/tests/tests/view/src/android/view/cts/SurfaceViewSyncTests.java
new file mode 100644
index 0000000..da453dd
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/SurfaceViewSyncTests.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.cts;
+
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.media.MediaPlayer;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.UiSelector;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.LinearInterpolator;
+import android.view.cts.surfacevalidator.AnimationFactory;
+import android.view.cts.surfacevalidator.AnimationTestCase;
+import android.view.cts.surfacevalidator.CapturedActivity;
+import android.view.cts.surfacevalidator.ViewFactory;
+import android.widget.FrameLayout;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+@SuppressLint("RtlHardcoded")
+public class SurfaceViewSyncTests {
+    private static final String TAG = "SurfaceViewSyncTests";
+    private static final int PERMISSION_DIALOG_WAIT_MS = 500;
+
+    @Before
+    public void setUp() throws UiObjectNotFoundException {
+        // The permission dialog will be auto-opened by the activity - find it and accept
+        UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        UiSelector acceptButtonSelector = new UiSelector().resourceId("android:id/button1");
+        UiObject acceptButton = uiDevice.findObject(acceptButtonSelector);
+        if (acceptButton.waitForExists(PERMISSION_DIALOG_WAIT_MS)) {
+            assertTrue(acceptButton.click());
+        }
+    }
+
+    private CapturedActivity getActivity() {
+        return (CapturedActivity) mActivityRule.getActivity();
+    }
+
+    private MediaPlayer getMediaPlayer() {
+        return getActivity().getMediaPlayer();
+    }
+
+    @Rule
+    public ActivityTestRule mActivityRule = new ActivityTestRule<>(CapturedActivity.class);
+
+    static ValueAnimator makeInfinite(ValueAnimator a) {
+        a.setRepeatMode(ObjectAnimator.REVERSE);
+        a.setRepeatCount(ObjectAnimator.INFINITE);
+        a.setDuration(200);
+        a.setInterpolator(new LinearInterpolator());
+        return a;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // ViewFactories
+    ///////////////////////////////////////////////////////////////////////////
+
+    private ViewFactory sEmptySurfaceViewFactory = SurfaceView::new;
+
+    private ViewFactory sGreenSurfaceViewFactory = context -> {
+        SurfaceView surfaceView = new SurfaceView(context);
+        surfaceView.getHolder().setFixedSize(640, 480);
+        surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
+            @Override
+            public void surfaceCreated(SurfaceHolder holder) {}
+
+            @Override
+            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+                Canvas canvas = holder.lockCanvas();
+                canvas.drawColor(Color.GREEN);
+                holder.unlockCanvasAndPost(canvas);
+            }
+
+            @Override
+            public void surfaceDestroyed(SurfaceHolder holder) {}
+        });
+        return surfaceView;
+    };
+
+    private ViewFactory sVideoViewFactory = context -> {
+        SurfaceView surfaceView = new SurfaceView(context);
+        surfaceView.getHolder().setFixedSize(640, 480);
+        surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
+            @Override
+            public void surfaceCreated(SurfaceHolder holder) {
+                getMediaPlayer().setSurface(holder.getSurface());
+                getMediaPlayer().start();
+            }
+
+            @Override
+            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
+
+            @Override
+            public void surfaceDestroyed(SurfaceHolder holder) {
+                getMediaPlayer().pause();
+                getMediaPlayer().setSurface(null);
+            }
+        });
+        return surfaceView;
+    };
+
+    ///////////////////////////////////////////////////////////////////////////
+    // AnimationFactories
+    ///////////////////////////////////////////////////////////////////////////
+
+    private AnimationFactory sSmallScaleAnimationFactory = view -> {
+        view.setPivotX(0);
+        view.setPivotY(0);
+        PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat(View.SCALE_X, 0.01f, 1f);
+        PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat(View.SCALE_Y, 0.01f, 1f);
+        return makeInfinite(ObjectAnimator.ofPropertyValuesHolder(view, pvhX, pvhY));
+    };
+
+    private AnimationFactory sBigScaleAnimationFactory = view -> {
+        view.setTranslationX(10);
+        view.setTranslationY(10);
+        view.setPivotX(0);
+        view.setPivotY(0);
+        PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat(View.SCALE_X, 1f, 3f);
+        PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat(View.SCALE_Y, 1f, 3f);
+        return makeInfinite(ObjectAnimator.ofPropertyValuesHolder(view, pvhX, pvhY));
+    };
+
+    private AnimationFactory sTranslateAnimationFactory = view -> {
+        PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat(View.TRANSLATION_X, 10f, 30f);
+        PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, 10f, 30f);
+        return makeInfinite(ObjectAnimator.ofPropertyValuesHolder(view, pvhX, pvhY));
+    };
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Tests
+    ///////////////////////////////////////////////////////////////////////////
+
+    /** Draws a moving 10x10 black rectangle, validates 100 pixels of black are seen each frame */
+    @Test
+    public void testSmallRect() {
+        CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
+                context -> new View(context) {
+                    // draw a single pixel
+                    final Paint sBlackPaint = new Paint();
+                    @Override
+                    protected void onDraw(Canvas canvas) {
+                        canvas.drawRect(0, 0, 10, 10, sBlackPaint);
+                    }
+
+                    @SuppressWarnings("unused")
+                    void setOffset(int offset) {
+                        // Note: offset by integer values, to ensure no rounding
+                        // is done in rendering layer, as that may be brittle
+                        setTranslationX(offset);
+                        setTranslationY(offset);
+                    }
+                },
+                new FrameLayout.LayoutParams(100, 100, Gravity.LEFT | Gravity.TOP),
+                view -> makeInfinite(ObjectAnimator.ofInt(view, "offset", 10, 30)),
+                (blackishPixelCount, width, height) -> blackishPixelCount == 100));
+        assertTrue(result.passFrames > 100);
+        assertTrue(result.failFrames == 0);
+    }
+
+    /**
+     * Verifies that a SurfaceView without a surface is entirely black, with pixel count being
+     * approximate to avoid rounding brittleness.
+     */
+    @Test
+    public void testEmptySurfaceView() {
+        CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
+                sEmptySurfaceViewFactory,
+                new FrameLayout.LayoutParams(100, 100, Gravity.LEFT | Gravity.TOP),
+                sTranslateAnimationFactory,
+                (blackishPixelCount, width, height) ->
+                        blackishPixelCount > 9000 && blackishPixelCount < 11000));
+        assertTrue(result.passFrames > 100);
+        assertTrue(result.failFrames == 0);
+    }
+
+    @Test
+    public void testSurfaceViewSmallScale() {
+        CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
+                sGreenSurfaceViewFactory,
+                new FrameLayout.LayoutParams(640, 480, Gravity.LEFT | Gravity.TOP),
+                sSmallScaleAnimationFactory,
+                (blackishPixelCount, width, height) -> blackishPixelCount == 0));
+        assertTrue(result.passFrames > 100);
+        assertTrue(result.failFrames == 0);
+    }
+
+    @Test
+    public void testSurfaceViewBigScale() {
+        CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
+                sGreenSurfaceViewFactory,
+                new FrameLayout.LayoutParams(640, 480, Gravity.LEFT | Gravity.TOP),
+                sBigScaleAnimationFactory,
+                (blackishPixelCount, width, height) -> blackishPixelCount == 0));
+        assertTrue(result.passFrames > 100);
+        assertTrue(result.failFrames == 0);
+    }
+
+    @Test
+    public void testVideoSurfaceViewTranslate() {
+        CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
+                sVideoViewFactory,
+                new FrameLayout.LayoutParams(640, 480, Gravity.LEFT | Gravity.TOP),
+                sTranslateAnimationFactory,
+                (blackishPixelCount, width, height) -> blackishPixelCount == 0));
+        assertTrue(result.passFrames > 100);
+        assertTrue(result.failFrames == 0);
+    }
+
+    @Test
+    public void testVideoSurfaceViewRotated() {
+        CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
+                sVideoViewFactory,
+                new FrameLayout.LayoutParams(100, 100, Gravity.LEFT | Gravity.TOP),
+                view -> makeInfinite(ObjectAnimator.ofPropertyValuesHolder(view,
+                        PropertyValuesHolder.ofFloat(View.TRANSLATION_X, 10f, 30f),
+                        PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, 10f, 30f),
+                        PropertyValuesHolder.ofFloat(View.ROTATION, 45f, 45f))),
+                (blackishPixelCount, width, height) -> blackishPixelCount == 0));
+        assertTrue(result.passFrames > 100);
+        assertTrue(result.failFrames == 0);
+    }
+
+    @Test
+    public void testVideoSurfaceViewEdgeCoverage() {
+        CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
+                sVideoViewFactory,
+                new FrameLayout.LayoutParams(640, 480, Gravity.CENTER),
+                view -> {
+                    ViewGroup parent = (ViewGroup) view.getParent();
+                    final int x = parent.getWidth() / 2;
+                    final int y = parent.getHeight() / 2;
+
+                    // Animate from left, to top, to right, to bottom
+                    return makeInfinite(ObjectAnimator.ofPropertyValuesHolder(view,
+                            PropertyValuesHolder.ofFloat(View.TRANSLATION_X, -x, 0, x, 0, -x),
+                            PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, 0, -y, 0, y, 0)));
+                },
+                (blackishPixelCount, width, height) -> blackishPixelCount == 0));
+        assertTrue(result.passFrames > 100);
+        assertTrue(result.failFrames == 0);
+    }
+
+    @Test
+    public void testVideoSurfaceViewCornerCoverage() {
+        CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
+                sVideoViewFactory,
+                new FrameLayout.LayoutParams(640, 480, Gravity.CENTER),
+                view -> {
+                    ViewGroup parent = (ViewGroup) view.getParent();
+                    final int x = parent.getWidth() / 2;
+                    final int y = parent.getHeight() / 2;
+
+                    // Animate from top left, to top right, to bottom right, to bottom left
+                    return makeInfinite(ObjectAnimator.ofPropertyValuesHolder(view,
+                            PropertyValuesHolder.ofFloat(View.TRANSLATION_X, -x, x, x, -x, -x),
+                            PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, -y, -y, y, y, -y)));
+                },
+                (blackishPixelCount, width, height) -> blackishPixelCount == 0));
+        assertTrue(result.passFrames > 100);
+        assertTrue(result.failFrames == 0);
+    }
+}
diff --git a/tests/tests/view/src/android/view/cts/surfacevalidator/AnimationFactory.java b/tests/tests/view/src/android/view/cts/surfacevalidator/AnimationFactory.java
new file mode 100644
index 0000000..c4c19cf
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/AnimationFactory.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.cts.surfacevalidator;
+
+import android.animation.ValueAnimator;
+import android.view.View;
+
+public interface AnimationFactory {
+    ValueAnimator createAnimator(View view);
+}
diff --git a/tests/tests/view/src/android/view/cts/surfacevalidator/AnimationTestCase.java b/tests/tests/view/src/android/view/cts/surfacevalidator/AnimationTestCase.java
new file mode 100644
index 0000000..6b455e2
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/AnimationTestCase.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.cts.surfacevalidator;
+
+import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.view.View;
+import android.widget.FrameLayout;
+
+public class AnimationTestCase {
+    private final ViewFactory mViewFactory;
+    private final FrameLayout.LayoutParams mLayoutParams;
+    private final AnimationFactory mAnimationFactory;
+    private final PixelChecker mPixelChecker;
+
+    private FrameLayout mParent;
+    private ValueAnimator mAnimator;
+
+    public AnimationTestCase(ViewFactory viewFactory,
+            FrameLayout.LayoutParams layoutParams,
+            AnimationFactory animationFactory,
+            PixelChecker pixelChecker) {
+        mViewFactory = viewFactory;
+        mLayoutParams = layoutParams;
+        mAnimationFactory = animationFactory;
+        mPixelChecker = pixelChecker;
+    }
+
+    PixelChecker getChecker() {
+        return mPixelChecker;
+    }
+
+    public void start(Context context, FrameLayout parent) {
+        mParent = parent;
+        mParent.removeAllViews();
+        View view = mViewFactory.createView(context);
+        mParent.addView(view, mLayoutParams);
+        mAnimator = mAnimationFactory.createAnimator(view);
+        mAnimator.start();
+    }
+
+    public void end() {
+        mAnimator.cancel();
+        mParent.removeAllViews();
+    }
+}
diff --git a/tests/tests/view/src/android/view/cts/surfacevalidator/CapturedActivity.java b/tests/tests/view/src/android/view/cts/surfacevalidator/CapturedActivity.java
new file mode 100644
index 0000000..a198136
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/CapturedActivity.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.cts.surfacevalidator;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.graphics.Point;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.media.MediaPlayer;
+import android.media.projection.MediaProjection;
+import android.media.projection.MediaProjectionManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Display;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import android.view.cts.R;
+
+public class CapturedActivity extends Activity {
+    public static class TestResult {
+        public int passFrames;
+        public int failFrames;
+    }
+
+    private static final String TAG = "CapturedActivity";
+    private static final long TIME_OUT_MS = 10000;
+    private static final int PERMISSION_CODE = 1;
+    private MediaProjectionManager mProjectionManager;
+    private MediaProjection mMediaProjection;
+    private VirtualDisplay mVirtualDisplay;
+
+    private SurfacePixelValidator mSurfacePixelValidator;
+    private final Object mLock = new Object();
+
+    private static final long START_CAPTURE_DELAY_MS = 1000;
+    private static final long END_CAPTURE_DELAY_MS = START_CAPTURE_DELAY_MS + 4000;
+    private static final long END_DELAY_MS = END_CAPTURE_DELAY_MS + 500;
+
+    private MediaPlayer mMediaPlayer;
+
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+    private volatile boolean mOnWatch;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        getWindow().getDecorView().setSystemUiVisibility(
+                View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN);
+
+        mProjectionManager =
+                (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
+
+        startActivityForResult(mProjectionManager.createScreenCaptureIntent(), PERMISSION_CODE);
+
+        mMediaPlayer = MediaPlayer.create(this, R.raw.colors_video);
+        mMediaPlayer.setLooping(true);
+
+        int uiMode = getResources().getConfiguration().uiMode;
+        mOnWatch = (uiMode & Configuration.UI_MODE_TYPE_WATCH) == Configuration.UI_MODE_TYPE_WATCH;
+    }
+
+    /**
+     * MediaPlayer pre-loaded with a video with no black pixels. Be kind, rewind.
+     */
+    public MediaPlayer getMediaPlayer() {
+        return mMediaPlayer;
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        Log.d(TAG, "onDestroy");
+        if (mMediaProjection != null) {
+            mMediaProjection.stop();
+            mMediaProjection = null;
+        }
+    }
+
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (requestCode != PERMISSION_CODE) {
+            throw new IllegalStateException("Unknown request code: " + requestCode);
+        }
+        if (resultCode != RESULT_OK) {
+            throw new IllegalStateException("User denied screen sharing permission");
+        }
+        mMediaProjection = mProjectionManager.getMediaProjection(resultCode, data);
+        mMediaProjection.registerCallback(new MediaProjectionCallback(), null);
+    }
+
+    public TestResult runTest(AnimationTestCase animationTestCase) {
+        TestResult testResult = new TestResult();
+        if (mOnWatch) {
+            /**
+             * Watch devices not supported, since they may not support:
+             *    1) displaying unmasked windows
+             *    2) RenderScript
+             *    3) Video playback
+             */
+            Log.d(TAG, "Skipping test on watch.");
+            testResult.passFrames = 1000;
+            testResult.failFrames = 0;
+            return testResult;
+        }
+
+        mHandler.post(() -> {
+            Log.d(TAG, "Setting up test case");
+
+            // shouldn't be necessary, since we've already done this in #create,
+            // but ensure status/nav are hidden for test
+            getWindow().getDecorView().setSystemUiVisibility(
+                    View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN);
+
+            animationTestCase.start(getApplicationContext(),
+                    (FrameLayout) findViewById(android.R.id.content));
+        });
+
+        mHandler.postDelayed(() -> {
+            Log.d(TAG, "Starting capture");
+
+            Display display = getWindow().getDecorView().getDisplay();
+            Point size = new Point();
+            DisplayMetrics metrics = new DisplayMetrics();
+            display.getRealSize(size);
+            display.getMetrics(metrics);
+
+            mSurfacePixelValidator = new SurfacePixelValidator(CapturedActivity.this,
+                    size, animationTestCase.getChecker());
+            Log.d("MediaProjection", "Size is " + size.toString());
+            mVirtualDisplay = mMediaProjection.createVirtualDisplay("ScreenSharingDemo",
+                    size.x, size.y,
+                    metrics.densityDpi,
+                    DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
+                    mSurfacePixelValidator.getSurface(),
+                    null /*Callbacks*/,
+                    null /*Handler*/);
+        }, START_CAPTURE_DELAY_MS);
+
+        mHandler.postDelayed(() -> {
+            Log.d(TAG, "Stopping capture");
+            mVirtualDisplay.release();
+            mVirtualDisplay = null;
+        }, END_CAPTURE_DELAY_MS);
+
+        mHandler.postDelayed(() -> {
+            Log.d(TAG, "Ending test case");
+            animationTestCase.end();
+            synchronized (mLock) {
+                mSurfacePixelValidator.finish(testResult);
+                mLock.notify();
+            }
+            mSurfacePixelValidator = null;
+        }, END_DELAY_MS);
+
+        synchronized (mLock) {
+            try {
+                mLock.wait(TIME_OUT_MS);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+        Log.d(TAG, "Test finished, passFrames " + testResult.passFrames
+                + ", failFrames " + testResult.failFrames);
+        return testResult;
+    }
+
+    private class MediaProjectionCallback extends MediaProjection.Callback {
+        @Override
+        public void onStop() {
+            Log.d(TAG, "MediaProjectionCallback#onStop");
+            if (mVirtualDisplay != null) {
+                mVirtualDisplay.release();
+                mVirtualDisplay = null;
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/view/src/android/view/cts/surfacevalidator/PixelChecker.java b/tests/tests/view/src/android/view/cts/surfacevalidator/PixelChecker.java
new file mode 100644
index 0000000..76f0adc
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/PixelChecker.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.cts.surfacevalidator;
+
+public interface PixelChecker {
+    boolean checkPixels(int blackishPixelCount, int width, int height);
+}
\ No newline at end of file
diff --git a/tests/tests/view/src/android/view/cts/surfacevalidator/PixelCounter.rs b/tests/tests/view/src/android/view/cts/surfacevalidator/PixelCounter.rs
new file mode 100644
index 0000000..55bc251
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/PixelCounter.rs
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma version(1)
+#pragma rs java_package_name(android.view.cts.surfacevalidator)
+
+int WIDTH;
+uchar THRESHOLD;
+
+rs_allocation image;
+
+void countBlackishPixels(const int32_t *v_in, int *v_out){
+    int y = v_in[0];
+    v_out[0] = 0;
+
+    for(int i = 0 ; i < WIDTH; i++){
+        uchar4 pixel = rsGetElementAt_uchar4(image, i, y);
+        if (pixel.r < THRESHOLD
+                && pixel.g < THRESHOLD
+                && pixel.b < THRESHOLD) {
+            v_out[0]++;
+        }
+    }
+}
diff --git a/tests/tests/view/src/android/view/cts/surfacevalidator/SurfacePixelValidator.java b/tests/tests/view/src/android/view/cts/surfacevalidator/SurfacePixelValidator.java
new file mode 100644
index 0000000..c9bff1d
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/SurfacePixelValidator.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.cts.surfacevalidator;
+
+import android.content.Context;
+import android.graphics.Point;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Trace;
+import android.renderscript.Allocation;
+import android.renderscript.Element;
+import android.renderscript.RenderScript;
+import android.renderscript.Type;
+import android.util.Log;
+import android.view.Surface;
+import android.view.cts.surfacevalidator.PixelChecker;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public class SurfacePixelValidator {
+    private static final String TAG = "SurfacePixelValidator";
+
+    /**
+     * Observed that first few frames have errors with SurfaceView placement, so we skip for now.
+     * b/29603849 tracking that issue.
+     */
+    private static final int NUM_FIRST_FRAMES_SKIPPED = 8;
+
+    // If no channel is greater than this value, pixel will be considered 'blackish'.
+    private static final short PIXEL_CHANNEL_THRESHOLD = 4;
+
+    private final int mWidth;
+    private final int mHeight;
+
+    private final HandlerThread mWorkerThread;
+    private final Handler mWorkerHandler;
+
+    private final PixelChecker mPixelChecker;
+
+    private final RenderScript mRS;
+
+    private final Allocation mInPixelsAllocation;
+    private final Allocation mInRowsAllocation;
+    private final Allocation mOutRowsAllocation;
+    private final ScriptC_PixelCounter mScript;
+
+
+    private final Object mResultLock = new Object();
+    private int mResultSuccessFrames;
+    private int mResultFailureFrames;
+
+    private Runnable mConsumeRunnable = new Runnable() {
+        int numSkipped = 0;
+        @Override
+        public void run() {
+            Trace.beginSection("consume buffer");
+            mInPixelsAllocation.ioReceive();
+            mScript.set_image(mInPixelsAllocation);
+            Trace.endSection();
+
+            Trace.beginSection("compare");
+            mScript.forEach_countBlackishPixels(mInRowsAllocation, mOutRowsAllocation);
+            Trace.endSection();
+
+            Trace.beginSection("sum");
+            int blackishPixelCount = sum1DIntAllocation(mOutRowsAllocation, mHeight);
+            Trace.endSection();
+
+            boolean success = mPixelChecker.checkPixels(blackishPixelCount, mWidth, mHeight);
+            synchronized (mResultLock) {
+                if (numSkipped < NUM_FIRST_FRAMES_SKIPPED) {
+                    numSkipped++;
+                } else {
+
+                    if (success) {
+                        mResultSuccessFrames++;
+                    } else {
+                        mResultFailureFrames++;
+                        int totalFramesSeen = mResultSuccessFrames + mResultFailureFrames;
+                        Log.d(TAG, "Failure (pixel count = " + blackishPixelCount
+                                + ") occurred on frame " + totalFramesSeen);
+                    }
+                }
+            }
+        }
+    };
+
+    public SurfacePixelValidator(Context context, Point size, PixelChecker pixelChecker) {
+        mWidth = size.x;
+        mHeight = size.y;
+
+        mWorkerThread = new HandlerThread("SurfacePixelValidator");
+        mWorkerThread.start();
+        mWorkerHandler = new Handler(mWorkerThread.getLooper());
+
+        mPixelChecker = pixelChecker;
+
+        mRS = RenderScript.create(context);
+        mScript = new ScriptC_PixelCounter(mRS);
+
+        mInPixelsAllocation = createBufferQueueAllocation();
+        mInRowsAllocation = createInputRowIndexAllocation();
+        mOutRowsAllocation = createOutputRowAllocation();
+        mScript.set_WIDTH(mWidth);
+        mScript.set_THRESHOLD(PIXEL_CHANNEL_THRESHOLD);
+
+        mInPixelsAllocation.setOnBufferAvailableListener(
+                allocation -> mWorkerHandler.post(mConsumeRunnable));
+    }
+
+    public Surface getSurface() {
+        return mInPixelsAllocation.getSurface();
+    }
+
+    static private int sum1DIntAllocation(Allocation array, int length) {
+        //Get the values returned from the function
+        int[] returnValue = new int[length];
+        array.copyTo(returnValue);
+        int sum = 0;
+        //If any row had any different pixels, then it fails
+        for (int i = 0; i < length; i++) {
+            sum += returnValue[i];
+        }
+        return sum;
+    }
+
+    /**
+     * Creates an allocation where the values in it are the indices of each row
+     */
+    private Allocation createInputRowIndexAllocation() {
+        //Create an array with the index of each row
+        int[] inputIndices = new int[mHeight];
+        for (int i = 0; i < mHeight; i++) {
+            inputIndices[i] = i;
+        }
+        //Create the allocation from that given array
+        Allocation inputAllocation = Allocation.createSized(mRS, Element.I32(mRS),
+                inputIndices.length, Allocation.USAGE_SCRIPT);
+        inputAllocation.copyFrom(inputIndices);
+        return inputAllocation;
+    }
+
+    private Allocation createOutputRowAllocation() {
+        return Allocation.createSized(mRS, Element.I32(mRS), mHeight, Allocation.USAGE_SCRIPT);
+    }
+
+    private Allocation createBufferQueueAllocation() {
+        return Allocation.createAllocations(mRS, Type.createXY(mRS,
+                Element.U8_4(mRS), mWidth, mHeight),
+                Allocation.USAGE_SCRIPT | Allocation.USAGE_IO_INPUT,
+                1)[0];
+    }
+
+    /**
+     * Shuts down processing pipeline, and returns current pass/fail counts.
+     *
+     * Wait for pipeline to flush before calling this method. If not, frames that are still in
+     * flight may be lost.
+     */
+    public void finish(CapturedActivity.TestResult testResult) {
+        synchronized (mResultLock) {
+            // could in theory miss results still processing, but only if latency is extremely high.
+            // Caller should only call this
+            testResult.failFrames = mResultFailureFrames;
+            testResult.passFrames = mResultSuccessFrames;
+        }
+        mWorkerThread.quitSafely();
+    }
+}
diff --git a/tests/tests/view/src/android/view/cts/surfacevalidator/ViewFactory.java b/tests/tests/view/src/android/view/cts/surfacevalidator/ViewFactory.java
new file mode 100644
index 0000000..9ef2ef8
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/ViewFactory.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.cts.surfacevalidator;
+
+import android.content.Context;
+import android.view.View;
+
+public interface ViewFactory {
+    View createView(Context context);
+}
diff --git a/tests/tests/view/src/android/view/inputmethod/cts/EditorInfoTest.java b/tests/tests/view/src/android/view/inputmethod/cts/EditorInfoTest.java
index e9c3058..0780460 100644
--- a/tests/tests/view/src/android/view/inputmethod/cts/EditorInfoTest.java
+++ b/tests/tests/view/src/android/view/inputmethod/cts/EditorInfoTest.java
@@ -21,6 +21,7 @@
 import android.os.LocaleList;
 import android.os.Parcel;
 import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
 import android.text.TextUtils;
 import android.util.Printer;
 import android.view.inputmethod.EditorInfo;
@@ -50,6 +51,7 @@
         b.putString(key, value);
         info.extras = b;
         info.hintLocales = LocaleList.forLanguageTags("en-PH,en-US");
+        info.contentMimeTypes = new String[]{"image/gif", "image/png"};
 
         assertEquals(0, info.describeContents());
 
@@ -73,6 +75,7 @@
         assertEquals(info.label.toString(), targetInfo.label.toString());
         assertEquals(info.extras.getString(key), targetInfo.extras.getString(key));
         assertEquals(info.hintLocales, targetInfo.hintLocales);
+        MoreAsserts.assertEquals(info.contentMimeTypes, targetInfo.contentMimeTypes);
 
         TestPrinter printer = new TestPrinter();
         String prefix = "TestEditorInfo";
diff --git a/tests/tests/view/src/android/view/inputmethod/cts/InputConnectionWrapperTest.java b/tests/tests/view/src/android/view/inputmethod/cts/InputConnectionWrapperTest.java
index 1ddfd2b..deab56e 100644
--- a/tests/tests/view/src/android/view/inputmethod/cts/InputConnectionWrapperTest.java
+++ b/tests/tests/view/src/android/view/inputmethod/cts/InputConnectionWrapperTest.java
@@ -17,6 +17,8 @@
 package android.view.inputmethod.cts;
 
 
+import android.content.ClipDescription;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
 import android.test.AndroidTestCase;
@@ -29,6 +31,7 @@
 import android.view.inputmethod.ExtractedTextRequest;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputConnectionWrapper;
+import android.view.inputmethod.InputContentInfo;
 
 public class InputConnectionWrapperTest extends AndroidTestCase {
 
@@ -94,6 +97,13 @@
         assertFalse(inputConnection.isGetHandlerCalled);
         assertNull(inputConnection.getHandler());
         assertTrue(inputConnection.isGetHandlerCalled);
+        assertFalse(inputConnection.isCommitContentCalled);
+        final InputContentInfo inputContentInfo = new InputContentInfo(
+                Uri.parse("content://com.example/path"),
+                new ClipDescription("sample content", new String[]{"image/png"}),
+                Uri.parse("https://example.com"));
+        assertTrue(inputConnection.commitContent(inputContentInfo, 0 /* flags */, null /* opt */));
+        assertTrue(inputConnection.isCommitContentCalled);
     }
 
     private class MockInputConnection implements InputConnection {
@@ -122,6 +132,7 @@
         public boolean isRequestCursorUpdatesCalled;
         public boolean isGetHandlerCalled;
         public boolean isCloseConnectionCalled;
+        public boolean isCommitContentCalled;
 
         public boolean beginBatchEdit() {
             isBeginBatchEditCalled = true;
@@ -246,5 +257,10 @@
         public void closeConnection() {
             isCloseConnectionCalled = true;
         }
+
+        public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
+            isCommitContentCalled = true;
+            return true;
+        }
     }
 }
diff --git a/tests/tests/view/src/android/view/inputmethod/cts/InputContentInfoTest.java b/tests/tests/view/src/android/view/inputmethod/cts/InputContentInfoTest.java
new file mode 100644
index 0000000..30c86bf
--- /dev/null
+++ b/tests/tests/view/src/android/view/inputmethod/cts/InputContentInfoTest.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod.cts;
+
+
+import android.content.ClipDescription;
+import android.net.Uri;
+import android.os.Parcel;
+import android.test.AndroidTestCase;
+import android.view.inputmethod.InputContentInfo;
+
+import java.lang.NullPointerException;
+import java.security.InvalidParameterException;
+
+public class InputContentInfoTest extends AndroidTestCase {
+
+    public void testInputContentInfo() {
+        InputContentInfo info = new InputContentInfo(
+                 Uri.parse("content://com.example/path"),
+                 new ClipDescription("sample content", new String[]{"image/png"}),
+                 Uri.parse("https://example.com"));
+
+        assertEquals(Uri.parse("content://com.example/path"), info.getContentUri());
+        assertEquals(1, info.getDescription().getMimeTypeCount());
+        assertEquals("image/png", info.getDescription().getMimeType(0));
+        assertEquals("sample content", info.getDescription().getLabel());
+        assertEquals(Uri.parse("https://example.com"), info.getLinkUri());
+
+        Parcel p = Parcel.obtain();
+        info.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        InputContentInfo targetInfo = InputContentInfo.CREATOR.createFromParcel(p);
+        p.recycle();
+
+        assertEquals(info.getContentUri(), targetInfo.getContentUri());
+        assertEquals(info.getDescription().getMimeTypeCount(),
+                targetInfo.getDescription().getMimeTypeCount());
+        assertEquals(info.getDescription().getMimeType(0),
+                targetInfo.getDescription().getMimeType(0));
+        assertEquals(info.getDescription().getLabel(), targetInfo.getDescription().getLabel());
+        assertEquals(info.getLinkUri(), targetInfo.getLinkUri());
+    }
+
+    public void testContentUri() {
+        try {
+            InputContentInfo info = new InputContentInfo(
+                    null, new ClipDescription("sample content", new String[]{"image/png"}),
+                    Uri.parse("https://example.com"));
+            fail("InputContentInfo must not accept a null content URI.");
+        } catch (NullPointerException e) {
+            // OK.
+        } catch (Exception e) {
+            fail("Unexpected exception=" + e);
+        }
+
+        try {
+            InputContentInfo info = new InputContentInfo(
+                    Uri.parse("https://example.com"),
+                    new ClipDescription("sample content", new String[]{"image/png"}),
+                    Uri.parse("https://example.com"));
+            fail("InputContentInfo must accept content URI only.");
+        } catch (InvalidParameterException e) {
+            // OK.
+        } catch (Exception e) {
+            fail("Unexpected exception=" + e);
+        }
+    }
+
+    public void testMimeType() {
+        try {
+            InputContentInfo info = new InputContentInfo(
+                     Uri.parse("content://com.example/path"), null,
+                     Uri.parse("https://example.com"));
+            fail("InputContentInfo must not accept a null description.");
+        } catch (NullPointerException e) {
+            // OK.
+        } catch (Exception e) {
+            fail("Unexpected exception=" + e);
+        }
+    }
+
+    public void testLinkUri() {
+        try {
+            InputContentInfo info = new InputContentInfo(
+                     Uri.parse("content://com.example/path"),
+                     new ClipDescription("sample content", new String[]{"image/png"}),
+                     null);
+        } catch (Exception e) {
+            fail("InputContentInfo must accept a null link Uri.");
+        }
+
+        try {
+            InputContentInfo info = new InputContentInfo(
+                     Uri.parse("content://com.example/path"),
+                     new ClipDescription("sample content", new String[]{"image/png"}),
+                     Uri.parse("http://example.com/path"));
+        } catch (Exception e) {
+            fail("InputContentInfo must accept http link Uri.");
+        }
+
+        try {
+            InputContentInfo info = new InputContentInfo(
+                     Uri.parse("content://com.example/path"),
+                     new ClipDescription("sample content", new String[]{"image/png"}),
+                     Uri.parse("https://example.com/path"));
+        } catch (Exception e) {
+            fail("InputContentInfo must accept https link Uri.");
+        }
+
+        try {
+            InputContentInfo info = new InputContentInfo(
+                     Uri.parse("content://com.example/path"),
+                     new ClipDescription("sample content", new String[]{"image/png"}),
+                     Uri.parse("ftp://example.com/path"));
+            fail("InputContentInfo must accept http and https link Uri only.");
+        } catch (InvalidParameterException e) {
+            // OK.
+        } catch (Exception e) {
+            fail("Unexpected exception=" + e);
+        }
+
+        try {
+            InputContentInfo info = new InputContentInfo(
+                     Uri.parse("content://com.example/path"),
+                     new ClipDescription("sample content", new String[]{"image/png"}),
+                     Uri.parse("content://com.example/path"));
+            fail("InputContentInfo must accept http and https link Uri only.");
+        } catch (InvalidParameterException e) {
+            // OK.
+        } catch (Exception e) {
+            fail("Unexpected exception=" + e);
+        }
+    }
+}
diff --git a/tests/tests/widget/src/android/widget/cts/ListViewTest.java b/tests/tests/widget/src/android/widget/cts/ListViewTest.java
index e89b3d3..41fb14b 100644
--- a/tests/tests/widget/src/android/widget/cts/ListViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ListViewTest.java
@@ -16,44 +16,58 @@
 
 package android.widget.cts;
 
-import android.widget.BaseAdapter;
-import android.widget.LinearLayout;
-import android.widget.cts.R;
+import junit.framework.Assert;
 
 import org.xmlpull.v1.XmlPullParser;
 
+import android.app.ActionBar.LayoutParams;
 import android.app.Activity;
 import android.app.Instrumentation;
 import android.content.Context;
 import android.cts.util.PollingCheck;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.test.ActivityInstrumentationTestCase2;
 import android.test.UiThreadTest;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.util.AttributeSet;
 import android.util.Pair;
+import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.util.Xml;
 import android.view.KeyEvent;
 import android.view.View;
+import android.view.View.MeasureSpec;
 import android.view.ViewGroup;
 import android.view.animation.LayoutAnimationController;
 import android.widget.AdapterView;
 import android.widget.AdapterView.OnItemClickListener;
 import android.widget.ArrayAdapter;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
 import android.widget.ListView;
 import android.widget.TextView;
+import android.widget.cts.R;
 import android.widget.cts.util.ViewTestUtils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
-import junit.framework.Assert;
-
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 
 public class ListViewTest extends ActivityInstrumentationTestCase2<ListViewCtsActivity> {
     private final String[] mCountryList = new String[] {
@@ -764,7 +778,45 @@
             mListView.setPadding(10, 0, 5, 0);
             assertFalse(view.isLayoutRequested());
         });
+    }
 
+    @MediumTest
+    public void testResolveRtlOnReAttach() {
+        View spacer = new View(getActivity());
+        spacer.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+                250));
+        final DummyAdapter adapter = new DummyAdapter(50, spacer);
+        ViewTestUtils.runOnMainAndDrawSync(getInstrumentation(), mListView, () -> {
+            mListView.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
+            mListView.setLayoutParams(new LinearLayout.LayoutParams(200, 150));
+            mListView.setAdapter(adapter);
+        });
+        assertEquals("test sanity", 1, mListView.getChildCount());
+        ViewTestUtils.runOnMainAndDrawSync(getInstrumentation(), mListView, () -> {
+            // we scroll in pieces because list view caps scroll by its height
+            mListView.scrollListBy(100);
+            mListView.scrollListBy(100);
+            mListView.scrollListBy(60);
+        });
+        assertEquals("test sanity", 1, mListView.getChildCount());
+        assertEquals("test sanity", 1, mListView.getFirstVisiblePosition());
+        ViewTestUtils.runOnMainAndDrawSync(getInstrumentation(), mListView, () -> {
+            mListView.scrollListBy(-100);
+            mListView.scrollListBy(-100);
+            mListView.scrollListBy(-60);
+        });
+        assertEquals("test sanity", 1, mListView.getChildCount());
+        assertEquals("item 0 should be visible", 0, mListView.getFirstVisiblePosition());
+        ViewTestUtils.runOnMainAndDrawSync(getInstrumentation(), mListView, () -> {
+            mListView.scrollListBy(100);
+            mListView.scrollListBy(100);
+            mListView.scrollListBy(60);
+        });
+        assertEquals("test sanity", 1, mListView.getChildCount());
+        assertEquals("test sanity", 1, mListView.getFirstVisiblePosition());
+
+        assertEquals("the view's RTL properties must be resolved",
+                mListView.getChildAt(0).getLayoutDirection(), View.LAYOUT_DIRECTION_RTL);
     }
 
     private class MockView extends View {
@@ -832,6 +884,94 @@
         assertEquals(childView2, listView.getChildAt(2));
     }
 
+    private static final int EXACTLY_500_PX = MeasureSpec.makeMeasureSpec(500, MeasureSpec.EXACTLY);
+
+    @MediumTest
+    public void testJumpDrawables() {
+        FrameLayout layout = new FrameLayout(mActivity);
+        ListView listView = new ListView(mActivity);
+        ArrayAdapterWithMockDrawable adapter = new ArrayAdapterWithMockDrawable(mActivity);
+        for (int i = 0; i < 50; i++) {
+            adapter.add(Integer.toString(i));
+        }
+
+        // Initial state should jump exactly once during attach.
+        mInstrumentation.runOnMainSync(() -> {
+            listView.setAdapter(adapter);
+            layout.addView(listView, new LayoutParams(LayoutParams.MATCH_PARENT, 200));
+            mActivity.setContentView(layout);
+        });
+        mInstrumentation.waitForIdleSync();
+        assertTrue("List is not showing any children", listView.getChildCount() > 0);
+        Drawable firstBackground = listView.getChildAt(0).getBackground();
+        verify(firstBackground, times(1)).jumpToCurrentState();
+
+        // Lay out views without recycling. This should not jump again.
+        mInstrumentation.runOnMainSync(() -> listView.requestLayout());
+        mInstrumentation.waitForIdleSync();
+        assertSame(firstBackground, listView.getChildAt(0).getBackground());
+        verify(firstBackground, times(1)).jumpToCurrentState();
+
+        // If we're on a really big display, we might be in a position where
+        // the position we're going to scroll to is already visible, in which
+        // case we won't be able to test jump behavior when recycling.
+        int lastVisiblePosition = listView.getLastVisiblePosition();
+        int targetPosition = adapter.getCount() - 1;
+        if (targetPosition <= lastVisiblePosition) {
+            return;
+        }
+
+        // Reset the call counts before continuing, since the backgrounds may
+        // be recycled from either views that were on-screen or in the scrap
+        // heap, and those would have slightly different call counts.
+        adapter.resetMockBackgrounds();
+
+        // Scroll so that we have new views on screen. This should jump at
+        // least once when the view is recycled in a new position (but may be
+        // more if it was recycled from a view that was previously on-screen).
+        mInstrumentation.runOnMainSync(() -> listView.setSelection(targetPosition));
+        mInstrumentation.waitForIdleSync();
+
+        View lastChild = listView.getChildAt(listView.getChildCount() - 1);
+        verify(lastChild.getBackground(), atLeast(1)).jumpToCurrentState();
+
+        // Reset the call counts before continuing.
+        adapter.resetMockBackgrounds();
+
+        // Scroll back to the top. This should jump at least once when the view
+        // is recycled in a new position (but may be more if it was recycled
+        // from a view that was previously on-screen).
+        mInstrumentation.runOnMainSync(() -> listView.setSelection(0));
+        mInstrumentation.waitForIdleSync();
+
+        View firstChild = listView.getChildAt(0);
+        verify(firstChild.getBackground(), atLeast(1)).jumpToCurrentState();
+    }
+
+    private static class ArrayAdapterWithMockDrawable extends ArrayAdapter<String> {
+        private SparseArray<Drawable> mBackgrounds = new SparseArray<>();
+
+        public ArrayAdapterWithMockDrawable(Context context) {
+            super(context, android.R.layout.simple_list_item_1);
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            final View view = super.getView(position, convertView, parent);
+            if (view.getBackground() == null) {
+                view.setBackground(spy(new ColorDrawable(Color.BLACK)));
+            }
+            return view;
+        }
+
+        public void resetMockBackgrounds() {
+            for (int i = 0; i < mBackgrounds.size(); i++) {
+                Drawable background = mBackgrounds.valueAt(i);
+                reset(background);
+            }
+        }
+    }
+
     private class TemporarilyDetachableMockView extends View {
 
         private boolean mIsDispatchingStartTemporaryDetach = false;
diff --git a/tools/cts-test-metrics/CtsCameraTestCases.reportlog.json b/tools/cts-test-metrics/CtsCameraTestCases.reportlog.json
new file mode 100644
index 0000000..81a9ef1
--- /dev/null
+++ b/tools/cts-test-metrics/CtsCameraTestCases.reportlog.json
@@ -0,0 +1 @@
+{"test_reprocessing_throughput":[{"camera_id":"0","format":35,"reprocess_type":"YUV reprocessing","capture_message":"capture latency","latency":[237.0,102.0,99.0,105.0,124.0,92.0],"camera_reprocessing_average_latency":126.5},{"camera_id":"0","format":34,"reprocess_type":"opaque reprocessing","capture_message":"capture latency","latency":[206.0,91.0,92.0,89.0,119.0,84.0],"camera_reprocessing_average_latency":113.5},{"camera_id":"1","format":35,"reprocess_type":"YUV reprocessing","capture_message":"capture latency","latency":[216.0,84.0,80.0,83.0,93.0,76.0],"camera_reprocessing_average_latency":105.33333333333333},{"camera_id":"1","format":34,"reprocess_type":"opaque reprocessing","capture_message":"capture latency","latency":[212.0,83.0,71.0,80.0,93.0,74.0],"camera_reprocessing_average_latency":102.16666666666667},{"camera_id":"0","format":35,"reprocess_type":"YUV reprocessing","capture_message":"capture latency","latency":[228.0,105.0,85.0,86.0,116.0,83.0],"camera_reprocessing_average_latency":117.16666666666667},{"camera_id":"0","format":34,"reprocess_type":"opaque reprocessing","capture_message":"capture latency","latency":[195.0,89.0,94.0,94.0,116.0,86.0],"camera_reprocessing_average_latency":112.33333333333333},{"camera_id":"1","format":35,"reprocess_type":"YUV reprocessing","capture_message":"capture latency","latency":[150.0,83.0,75.0,75.0,102.0,76.0],"camera_reprocessing_average_latency":93.5},{"camera_id":"1","format":34,"reprocess_type":"opaque reprocessing","capture_message":"capture latency","latency":[198.0,85.0,78.0,71.0,95.0,77.0],"camera_reprocessing_average_latency":100.66666666666667}],"test_camera_launch_average":[{"camera_launch_average_time_for_all_cameras":326.1},{"camera_launch_average_time_for_all_cameras":321.8}],"test_reprocessing_latency":[{"camera_id":"0","format":35,"reprocess_type":"YUV reprocessing","capture_message":"shot to shot latency","latency":[303.0,254.0,259.0,196.0,201.0,195.0],"camera_reprocessing_shot_to_shot_average_latency":234.66666666666666},{"camera_id":"0","format":34,"reprocess_type":"opaque reprocessing","capture_message":"shot to shot latency","latency":[248.0,172.0,209.0,188.0,201.0,204.0],"camera_reprocessing_shot_to_shot_average_latency":203.66666666666666},{"camera_id":"1","format":35,"reprocess_type":"YUV reprocessing","capture_message":"shot to shot latency","latency":[190.0,238.0,220.0,213.0,144.0,154.0],"camera_reprocessing_shot_to_shot_average_latency":193.16666666666666},{"camera_id":"1","format":34,"reprocess_type":"opaque reprocessing","capture_message":"shot to shot latency","latency":[237.0,166.0,153.0,148.0,162.0,140.0],"camera_reprocessing_shot_to_shot_average_latency":167.66666666666666},{"camera_id":"0","format":35,"reprocess_type":"YUV reprocessing","capture_message":"shot to shot latency","latency":[302.0,262.0,256.0,197.0,200.0,201.0],"camera_reprocessing_shot_to_shot_average_latency":236.33333333333334},{"camera_id":"0","format":34,"reprocess_type":"opaque reprocessing","capture_message":"shot to shot latency","latency":[251.0,166.0,199.0,199.0,213.0,201.0],"camera_reprocessing_shot_to_shot_average_latency":204.83333333333334},{"camera_id":"1","format":35,"reprocess_type":"YUV reprocessing","capture_message":"shot to shot latency","latency":[199.0,153.0,159.0,164.0,152.0,166.0],"camera_reprocessing_shot_to_shot_average_latency":165.5},{"camera_id":"1","format":34,"reprocess_type":"opaque reprocessing","capture_message":"shot to shot latency","latency":[210.0,143.0,161.0,162.0,158.0,156.0],"camera_reprocessing_shot_to_shot_average_latency":165.0}],"test_high_quality_reprocessing_latency":[{"camera_id":"0","format":35,"reprocess_type":"YUV reprocessing","capture_message":"shot to shot latency for High Quality noise reduction and edge modes","latency":[479.0,398.0,351.0,487.0,461.0,395.0],"camera_reprocessing_shot_to_shot_average_latency":428.5},{"camera_id":"0","format":34,"reprocess_type":"opaque reprocessing","capture_message":"shot to shot latency for High Quality noise reduction and edge modes","latency":[355.0,324.0,335.0,334.0,336.0,347.0],"camera_reprocessing_shot_to_shot_average_latency":338.5},{"camera_id":"1","format":35,"reprocess_type":"YUV reprocessing","capture_message":"shot to shot latency for High Quality noise reduction and edge modes","latency":[235.0,220.0,223.0,228.0,222.0,227.0],"camera_reprocessing_shot_to_shot_average_latency":225.83333333333334},{"camera_id":"1","format":34,"reprocess_type":"opaque reprocessing","capture_message":"shot to shot latency for High Quality noise reduction and edge modes","latency":[256.0,186.0,230.0,215.0,226.0,242.0],"camera_reprocessing_shot_to_shot_average_latency":225.83333333333334},{"camera_id":"0","format":35,"reprocess_type":"YUV reprocessing","capture_message":"shot to shot latency for High Quality noise reduction and edge modes","latency":[415.0,327.0,336.0,340.0,323.0,332.0],"camera_reprocessing_shot_to_shot_average_latency":345.5},{"camera_id":"0","format":34,"reprocess_type":"opaque reprocessing","capture_message":"shot to shot latency for High Quality noise reduction and edge modes","latency":[381.0,302.0,331.0,332.0,336.0,333.0],"camera_reprocessing_shot_to_shot_average_latency":335.8333333333333},{"camera_id":"1","format":35,"reprocess_type":"YUV reprocessing","capture_message":"shot to shot latency for High Quality noise reduction and edge modes","latency":[231.0,222.0,223.0,227.0,227.0,223.0],"camera_reprocessing_shot_to_shot_average_latency":225.5},{"camera_id":"1","format":34,"reprocess_type":"opaque reprocessing","capture_message":"shot to shot latency for High Quality noise reduction and edge modes","latency":[275.0,178.0,222.0,224.0,249.0,204.0],"camera_reprocessing_shot_to_shot_average_latency":225.33333333333334}],"test_single_capture":[{"camera_id":"0","camera_capture_latency":[476.0,639.0,654.0,639.0,665.0],"camera_capture_result_latency":[260.0,465.0,490.0,471.0,474.0]},{"camera_id":"1","camera_capture_latency":[461.0,639.0,627.0,637.0,631.0],"camera_capture_result_latency":[341.0,530.0,533.0,533.0,535.0]},{"camera_id":"0","camera_capture_latency":[465.0,643.0,660.0,649.0,642.0],"camera_capture_result_latency":[251.0,467.0,491.0,474.0,475.0]},{"camera_id":"1","camera_capture_latency":[457.0,541.0,533.0,546.0,534.0],"camera_capture_result_latency":[338.0,475.0,467.0,477.0,471.0]}],"test_single_capture_average":[{"camera_capture_result_average_latency_for_all_cameras":463.2},{"camera_capture_result_average_latency_for_all_cameras":438.6}],"test_reprocessing_capture_stall":[{"camera_id":"0","format":35,"reprocess_type":"YUV reprocessing","max_capture_timestamp_gaps":[66.929849,66.927076,66.827072],"capture_average_frame_duration":[66.742792,66.742792,66.742792],"camera_reprocessing_average_max_capture_timestamp_gaps":66.89466566666665},{"camera_id":"0","format":34,"reprocess_type":"opaque reprocessing","max_capture_timestamp_gaps":[66.838494,66.862969,67.054342],"capture_average_frame_duration":[66.742792,66.742792,66.742792],"camera_reprocessing_average_max_capture_timestamp_gaps":66.91860166666667},{"camera_id":"1","format":35,"reprocess_type":"YUV reprocessing","max_capture_timestamp_gaps":[75.091,75.156,75.092],"capture_average_frame_duration":[75.08460800000003,75.08460800000003,75.08460800000003],"camera_reprocessing_average_max_capture_timestamp_gaps":75.113},{"camera_id":"1","format":34,"reprocess_type":"opaque reprocessing","max_capture_timestamp_gaps":[75.096,75.09,75.091],"capture_average_frame_duration":[75.08460800000003,75.08460800000003,75.08460800000003],"camera_reprocessing_average_max_capture_timestamp_gaps":75.09233333333333},{"camera_id":"0","format":35,"reprocess_type":"YUV reprocessing","max_capture_timestamp_gaps":[66.810656,67.101617,66.811857],"capture_average_frame_duration":[66.742792,66.742792,66.742792],"camera_reprocessing_average_max_capture_timestamp_gaps":66.90804333333334},{"camera_id":"0","format":34,"reprocess_type":"opaque reprocessing","max_capture_timestamp_gaps":[133.322575,66.919741,66.95088],"capture_average_frame_duration":[66.742792,66.742792,66.742792],"camera_reprocessing_average_max_capture_timestamp_gaps":89.06439866666666},{"camera_id":"1","format":35,"reprocess_type":"YUV reprocessing","max_capture_timestamp_gaps":[80.088,80.091,80.089],"capture_average_frame_duration":[80.08507200000001,80.08507200000001,80.08507200000001],"camera_reprocessing_average_max_capture_timestamp_gaps":80.08933333333333},{"camera_id":"1","format":34,"reprocess_type":"opaque reprocessing","max_capture_timestamp_gaps":[80.092,80.091,80.091],"capture_average_frame_duration":[80.08507200000001,80.08507200000001,80.08507200000001],"camera_reprocessing_average_max_capture_timestamp_gaps":80.09133333333334}],"test_camera_launch":[{"camera_id":"0","camera_open_time":[116.0,85.0,88.0,83.0,86.0],"camera_configure_stream_time":[20.0,11.0,11.0,10.0,10.0],"camera_start_preview_time":[312.0,224.0,223.0,219.0,230.0],"camera_camera_stop_preview":[278.0,255.0,265.0,268.0,267.0],"camera_camera_close_time":[107.0,124.0,103.0,108.0,127.0],"camera_launch_time":[448.0,320.0,322.0,312.0,326.0]},{"camera_id":"1","camera_open_time":[72.0,67.0,67.0,67.0,65.0],"camera_configure_stream_time":[12.0,10.0,11.0,10.0,11.0],"camera_start_preview_time":[227.0,231.0,224.0,226.0,233.0],"camera_camera_stop_preview":[167.0,162.0,171.0,170.0,168.0],"camera_camera_close_time":[96.0,87.0,91.0,85.0,90.0],"camera_launch_time":[311.0,308.0,302.0,303.0,309.0]},{"camera_id":"0","camera_open_time":[96.0,85.0,89.0,89.0,84.0],"camera_configure_stream_time":[14.0,10.0,10.0,10.0,10.0],"camera_start_preview_time":[262.0,220.0,224.0,221.0,226.0],"camera_camera_stop_preview":[259.0,251.0,271.0,257.0,265.0],"camera_camera_close_time":[117.0,153.0,120.0,122.0,118.0],"camera_launch_time":[372.0,315.0,323.0,320.0,320.0]},{"camera_id":"1","camera_open_time":[71.0,67.0,68.0,70.0,69.0],"camera_configure_stream_time":[11.0,10.0,10.0,10.0,10.0],"camera_start_preview_time":[228.0,235.0,233.0,237.0,239.0],"camera_camera_stop_preview":[167.0,169.0,169.0,162.0,173.0],"camera_camera_close_time":[95.0,89.0,93.0,94.0,103.0],"camera_launch_time":[310.0,312.0,311.0,317.0,318.0]}],"test_high_quality_reprocessing_throughput":[{"camera_id":"0","format":35,"reprocess_type":"YUV reprocessing","capture_message":"capture latency for High Quality noise reduction and edge modes","latency":[272.0,246.0,211.0,210.0,235.0,202.0],"camera_reprocessing_average_latency":229.33333333333334},{"camera_id":"0","format":34,"reprocess_type":"opaque reprocessing","capture_message":"capture latency for High Quality noise reduction and edge modes","latency":[304.0,210.0,206.0,209.0,226.0,229.0],"camera_reprocessing_average_latency":230.66666666666666},{"camera_id":"1","format":35,"reprocess_type":"YUV reprocessing","capture_message":"capture latency for High Quality noise reduction and edge modes","latency":[246.0,123.0,116.0,114.0,131.0,109.0],"camera_reprocessing_average_latency":139.83333333333334},{"camera_id":"1","format":34,"reprocess_type":"opaque reprocessing","capture_message":"capture latency for High Quality noise reduction and edge modes","latency":[211.0,112.0,116.0,112.0,123.0,110.0],"camera_reprocessing_average_latency":130.66666666666666},{"camera_id":"0","format":35,"reprocess_type":"YUV reprocessing","capture_message":"capture latency for High Quality noise reduction and edge modes","latency":[349.0,215.0,214.0,214.0,238.0,213.0],"camera_reprocessing_average_latency":240.5},{"camera_id":"0","format":34,"reprocess_type":"opaque reprocessing","capture_message":"capture latency for High Quality noise reduction and edge modes","latency":[326.0,206.0,204.0,206.0,225.0,232.0],"camera_reprocessing_average_latency":233.16666666666666},{"camera_id":"1","format":35,"reprocess_type":"YUV reprocessing","capture_message":"capture latency for High Quality noise reduction and edge modes","latency":[238.0,119.0,130.0,116.0,130.0,114.0],"camera_reprocessing_average_latency":141.16666666666666},{"camera_id":"1","format":34,"reprocess_type":"opaque reprocessing","capture_message":"capture latency for High Quality noise reduction and edge modes","latency":[249.0,117.0,122.0,113.0,129.0,119.0],"camera_reprocessing_average_latency":141.5}]}
\ No newline at end of file
diff --git a/tools/cts-test-metrics/CtsUiHostTestCases.reportlog.json b/tools/cts-test-metrics/CtsUiHostTestCases.reportlog.json
new file mode 100644
index 0000000..6355fe3
--- /dev/null
+++ b/tools/cts-test-metrics/CtsUiHostTestCases.reportlog.json
@@ -0,0 +1 @@
+{"test_install_time":[{"install_time":[1950.0,1722.0,1762.0,1678.0,1738.0,1694.0,1787.0,1797.0,1799.0,1786.0],"install_time_average":1771.3},{"install_time":[1976.0,1986.0,1930.0,1729.0,1859.0,1875.0,1904.0,1805.0,1748.0,1875.0],"install_time_average":1868.7}]}
\ No newline at end of file
diff --git a/tools/cts-test-metrics/README b/tools/cts-test-metrics/README
new file mode 100644
index 0000000..cb68f3a
--- /dev/null
+++ b/tools/cts-test-metrics/README
@@ -0,0 +1,14 @@
+The parse_test_metrics.py script can be used to parse test metrics json files. Run the following
+command to see a demo:
+python parse_test_metrics.py CtsCameraTestCases.reportlog.json
+
+To parse multiple files, list all files as arguments. Try the following:
+python parse_test_metrics.py CtsCameraTestCases.reportlog.json CtsUiHostTestCases.reportlog.json
+python parse_test_metrics.py *.json
+
+Test metrics json files can be found in $CTS_ROOT/repository/results/$RESULT_DIR/report-log-files/
+directory.
+
+The MetricsParser class defines functions to parse a json file. The _Parse function takes a filename
+as input, reads the json file and adds the json object to json_data. The _PrintJson function
+takes the filename and corresponding json_data and prints out the streams as key, value pairs.
diff --git a/tools/cts-test-metrics/parse_test_metrics.py b/tools/cts-test-metrics/parse_test_metrics.py
new file mode 100755
index 0000000..839e372
--- /dev/null
+++ b/tools/cts-test-metrics/parse_test_metrics.py
@@ -0,0 +1,58 @@
+#!/usr/bin/python
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import argparse, json, sys
+
+class MetricsParser(object):
+  """Executor of this utility"""
+
+  def __init__(self):
+    self._parser = argparse.ArgumentParser('Parse CTS Test metrics jsons')
+    self._parser.add_argument('filenames', metavar='filenames', nargs='+',
+                              help='filenames of metrics jsons to be parsed')
+    self._metrics = []
+
+  def _ParseArgs(self):
+    self._args = self._parser.parse_args()
+
+  def _Parse(self, filename):
+    json_file = open(filename)
+    json_data = json.load(json_file)
+    self._metrics.append(json_data)
+    self._PrintJson(filename, json_data)
+
+  def _PrintJson(self, filename, json_data):
+    print "\nFilename: %s" % filename
+    stream_names = json_data.keys()
+    for stream_name in stream_names:
+      metrics_list = json_data.get(stream_name)
+      for metrics in metrics_list:
+        print "\nStream Name: %s" % stream_name
+        for key in metrics.keys():
+          print "Key: %s \t Value: %s" % (key, str(metrics.get(key)))
+
+  def Run(self):
+    self._ParseArgs()
+    try:
+      for filename in self._args.filenames:
+        self._Parse(filename)
+    except (IOError, ValueError) as e:
+      print >> sys.stderr, e
+      raise KeyboardInterrupt
+
+if __name__ == '__main__':
+  MetricsParser().Run()
+
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/util/ReportLogUtil.java b/tools/tradefed-host/src/com/android/cts/tradefed/util/ReportLogUtil.java
index 6ab175f..9233a4a 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/util/ReportLogUtil.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/util/ReportLogUtil.java
@@ -43,7 +43,6 @@
             // Delete earlier report logs if present on device.
             String command = String.format("adb -s %s shell rm -rf %s", device.getSerialNumber(),
                     SRC_DIR);
-            CLog.e(command);
             if (device.doesFileExist(SRC_DIR)) {
                 Process process = Runtime.getRuntime().exec(new String[]{"/bin/bash", "-c",
                         command});