Merge "Require sim-card-with-cert token for carrier api test." into nyc-dev
am: 234e9a28ee

Change-Id: I10a2bdc8f70d84c998b536196e8bdc314f82f395
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 a70f357..e53af21 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 39e3c38..2da18ee 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
@@ -153,8 +155,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
@@ -179,14 +181,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 2978b06..f227ff3 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,8 +28,14 @@
 
 LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
 
-LOCAL_SHARED_LIBRARIES := liblog
+LOCAL_CXX_STL := libc++_static
+
+LOCAL_SHARED_LIBRARIES := liblog \
+		libnativehelper_compat_libc++
+
+LOCAL_CXX_STL := libstdc++
 
 LOCAL_CXX_STL := libstdc++
 
 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/device-side/device-info/src/com/android/compatibility/common/deviceinfo/GlesStubActivity.java b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/GlesStubActivity.java
index c9d1e9c..ef95b66 100644
--- a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/GlesStubActivity.java
+++ b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/GlesStubActivity.java
@@ -29,10 +29,13 @@
 import android.opengl.GLSurfaceView;
 import android.util.Log;
 
+import java.lang.reflect.Field;
+import java.nio.FloatBuffer;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
 import java.util.HashSet;
+import java.util.HashMap;
 import java.util.Scanner;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
@@ -49,6 +52,8 @@
     private CountDownLatch mDone = new CountDownLatch(1);
     private HashSet<String> mOpenGlExtensions = new HashSet<String>();
     private HashSet<String> mFormats = new HashSet<String>();
+    private HashMap<String, Object> mImplVariables = new HashMap<String, Object>();
+    private HashSet<String> mDynamicArrayVariables = new HashSet<String>();
     private String mGraphicsVendor;
     private String mGraphicsRenderer;
 
@@ -145,6 +150,25 @@
         mGraphicsRenderer = renderer;
     }
 
+    public Set<String> getImplementationVariableNames() {
+        return mImplVariables.keySet();
+    }
+
+    public Object getImplementationVariable(String name) {
+        return mImplVariables.get(name);
+    }
+
+    public boolean isDynamicArrayVariable(String name) {
+        return mDynamicArrayVariables.contains(name);
+    }
+
+    void addImplementationVariable(String name, Object value, boolean isDynamicArray) {
+        mImplVariables.put(name, value);
+        if (isDynamicArray) {
+            mDynamicArrayVariables.add(name);
+        }
+    }
+
     static class GlesSurfaceView extends GLSurfaceView {
 
         public GlesSurfaceView(GlesStubActivity parent, int glVersion, CountDownLatch done) {
@@ -158,6 +182,200 @@
         }
     }
 
+    static abstract class ImplementationVariable {
+        private Field mField;
+        public ImplementationVariable(String fieldName) {
+            try {
+                mField = GLES30.class.getField(fieldName);
+            } catch (NoSuchFieldException e) {
+                Log.e(LOG_TAG, "Failed to get field reflection", e);
+            }
+        }
+
+        public String getName() {
+            return mField.getName();
+        }
+
+        public int getFieldIdValue() throws IllegalAccessException {
+            return mField.getInt(null);
+        }
+
+        abstract public Object getValue();
+
+        static protected int[] getIntValues(int fieldId, int count) throws IllegalAccessException{
+            int[] resultInts = new int[count];
+            GLES20.glGetIntegerv(fieldId, resultInts, 0);
+            return resultInts;
+        }
+    }
+
+    static class IntVectorValue extends ImplementationVariable {
+        private int mCount;
+
+        public IntVectorValue(String fieldName, int count) {
+            super(fieldName);
+            mCount = count;
+        }
+
+        @Override
+        public Object getValue() {
+            Log.i(LOG_TAG, "Getting : " + this.getName() + " " + mCount);
+            try {
+                return getIntValues(this.getFieldIdValue(), mCount);
+            } catch (IllegalAccessException e) {
+                Log.e(LOG_TAG, "Failed to read the GL field", e);
+            }
+            return null;
+        }
+    }
+
+    static class DynamicIntVectorValue extends ImplementationVariable {
+        private Field mCountField;
+
+        public DynamicIntVectorValue(String fieldName, String countFieldName) {
+            super(fieldName);
+            try {
+                mCountField = GLES30.class.getField(countFieldName);
+            } catch (NoSuchFieldException e) {
+                Log.e(LOG_TAG, "Failed to get field reflection", e);
+            }
+        }
+
+        @Override
+        public Object getValue() {
+            Log.i(LOG_TAG, "Getting : " + this.getName() + " " + mCountField.getName());
+            try {
+                int[] count = new int[] {0};
+                GLES20.glGetIntegerv(mCountField.getInt(null), count, 0);
+                Log.i(LOG_TAG, "Getting : " + mCountField.getName() + " " + count[0]);
+                return getIntValues(this.getFieldIdValue(), count[0]);
+            } catch (IllegalAccessException e) {
+                Log.e(LOG_TAG, "Failed to read the GL field", e);
+            }
+            return null;
+        }
+    }
+
+    static class FloatVectorValue extends ImplementationVariable {
+        private int mCount;
+
+        public FloatVectorValue(String fieldName, int count) {
+            super(fieldName);
+            mCount = count;
+        }
+
+        @Override
+        public Object getValue() {
+            Log.i(LOG_TAG, "Getting : " + this.getName() + " " + mCount);
+            try {
+                float[] result = new float[mCount];
+                GLES20.glGetFloatv(getFieldIdValue(), result, 0);
+                return result;
+            } catch (IllegalAccessException e) {
+                Log.e(LOG_TAG, "Failed to read the GL field", e);
+            }
+            return null;
+        }
+    }
+
+    static class LongVectorValue extends ImplementationVariable {
+        private int mCount;
+
+        public LongVectorValue(String fieldName, int count) {
+            super(fieldName);
+            mCount = count;
+        }
+
+        @Override
+        public Object getValue() {
+            Log.i(LOG_TAG, "Getting : " + this.getName() + " " + mCount);
+            try {
+                long result[] = new long[mCount];
+                GLES30.glGetInteger64v(getFieldIdValue(), result, 0);
+                return result;
+            } catch (IllegalAccessException e) {
+                Log.e(LOG_TAG, "Failed to read the GL field", e);
+            }
+            return null;
+        }
+    }
+
+    static class StringValue extends ImplementationVariable {
+        public StringValue(String fieldName) {
+            super(fieldName);
+        }
+
+        @Override
+        public Object getValue() {
+            Log.i(LOG_TAG, "Getting : " + this.getName());
+            String result = null;
+            try {
+                result = GLES20.glGetString(this.getFieldIdValue());
+            } catch (IllegalAccessException e) {
+                Log.e(LOG_TAG, "Failed to read the GL field", e);
+            }
+            return result;
+        }
+    }
+
+	// NOTE: Changes to the types of the variables will carry over to
+    // GraphicsDeviceInfo proto via GraphicsDeviceInfo. See
+    // go/edi-userguide for details.
+    static ImplementationVariable[] GLES2_IMPLEMENTATION_VARIABLES = {
+        new IntVectorValue("GL_SUBPIXEL_BITS", 1),
+        new IntVectorValue("GL_MAX_TEXTURE_SIZE", 1),
+        new IntVectorValue("GL_MAX_CUBE_MAP_TEXTURE_SIZE", 1),
+        new IntVectorValue("GL_MAX_VIEWPORT_DIMS", 2),
+        new FloatVectorValue("GL_ALIASED_POINT_SIZE_RANGE", 2),
+        new FloatVectorValue("GL_ALIASED_LINE_WIDTH_RANGE", 2),
+        new DynamicIntVectorValue("GL_COMPRESSED_TEXTURE_FORMATS", "GL_NUM_COMPRESSED_TEXTURE_FORMATS"),
+        new DynamicIntVectorValue("GL_SHADER_BINARY_FORMATS", "GL_NUM_SHADER_BINARY_FORMATS"),
+        new IntVectorValue("GL_SHADER_COMPILER", 1),
+        new StringValue("GL_SHADING_LANGUAGE_VERSION"),
+        new StringValue("GL_VERSION"),
+        new IntVectorValue("GL_MAX_VERTEX_ATTRIBS", 1),
+        new IntVectorValue("GL_MAX_VERTEX_UNIFORM_VECTORS", 1),
+        new IntVectorValue("GL_MAX_VARYING_VECTORS", 1),
+        new IntVectorValue("GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS", 1),
+        new IntVectorValue("GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS", 1),
+        new IntVectorValue("GL_MAX_TEXTURE_IMAGE_UNITS", 1),
+        new IntVectorValue("GL_MAX_FRAGMENT_UNIFORM_VECTORS", 1),
+        new IntVectorValue("GL_MAX_RENDERBUFFER_SIZE", 1)
+    };
+
+    static ImplementationVariable[] GLES3_IMPLEMENTATION_VARIABLES = {
+        new LongVectorValue("GL_MAX_ELEMENT_INDEX", 1),
+        new IntVectorValue("GL_MAX_3D_TEXTURE_SIZE", 1),
+        new IntVectorValue("GL_MAX_ARRAY_TEXTURE_LAYERS", 1),
+        new FloatVectorValue("GL_MAX_TEXTURE_LOD_BIAS", 1),
+        new IntVectorValue("GL_MAX_DRAW_BUFFERS", 1),
+        new IntVectorValue("GL_MAX_COLOR_ATTACHMENTS", 1),
+        new IntVectorValue("GL_MAX_ELEMENTS_INDICES", 1),
+        new IntVectorValue("GL_MAX_ELEMENTS_VERTICES", 1),
+        new DynamicIntVectorValue("GL_PROGRAM_BINARY_FORMATS", "GL_NUM_PROGRAM_BINARY_FORMATS"),
+        new LongVectorValue("GL_MAX_SERVER_WAIT_TIMEOUT", 1),
+        new IntVectorValue("GL_MAJOR_VERSION", 1),
+        new IntVectorValue("GL_MINOR_VERSION", 1),
+        new IntVectorValue("GL_MAX_VERTEX_UNIFORM_COMPONENTS", 1),
+        new IntVectorValue("GL_MAX_VERTEX_UNIFORM_BLOCKS", 1),
+        new IntVectorValue("GL_MAX_VERTEX_OUTPUT_COMPONENTS", 1),
+        new IntVectorValue("GL_MAX_FRAGMENT_UNIFORM_COMPONENTS", 1),
+        new IntVectorValue("GL_MAX_FRAGMENT_UNIFORM_BLOCKS", 1),
+        new IntVectorValue("GL_MAX_FRAGMENT_INPUT_COMPONENTS", 1),
+        new IntVectorValue("GL_MIN_PROGRAM_TEXEL_OFFSET", 1),
+        new IntVectorValue("GL_MAX_PROGRAM_TEXEL_OFFSET", 1),
+        new IntVectorValue("GL_MAX_UNIFORM_BUFFER_BINDINGS", 1),
+        new LongVectorValue("GL_MAX_UNIFORM_BLOCK_SIZE", 1),
+        new IntVectorValue("GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT", 1),
+        new IntVectorValue("GL_MAX_COMBINED_UNIFORM_BLOCKS", 1),
+        new LongVectorValue("GL_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS", 1),
+        new LongVectorValue("GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS", 1),
+        new IntVectorValue("GL_MAX_VARYING_COMPONENTS", 1),
+        new IntVectorValue("GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS", 1),
+        new IntVectorValue("GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS", 1),
+        new IntVectorValue("GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS", 1)
+    };
+
     static class OpenGlesRenderer implements GLSurfaceView.Renderer {
 
         private final GlesStubActivity mParent;
@@ -179,10 +397,12 @@
                 extensions = GLES20.glGetString(GLES20.GL_EXTENSIONS);
                 vendor = GLES20.glGetString(GLES20.GL_VENDOR);
                 renderer = GLES20.glGetString(GLES20.GL_RENDERER);
+                collectImplementationVariables(GLES2_IMPLEMENTATION_VARIABLES);
             } else if (mGlVersion == 3) {
                 extensions = GLES30.glGetString(GLES30.GL_EXTENSIONS);
                 vendor = GLES30.glGetString(GLES30.GL_VENDOR);
                 renderer = GLES30.glGetString(GLES30.GL_RENDERER);
+                collectImplementationVariables(GLES3_IMPLEMENTATION_VARIABLES);
             } else {
                 extensions = gl.glGetString(GL10.GL_EXTENSIONS);
                 vendor = gl.glGetString(GL10.GL_VENDOR);
@@ -202,6 +422,7 @@
                 }
             }
             scanner.close();
+
             mDone.countDown();
         }
 
@@ -210,5 +431,14 @@
 
         @Override
         public void onDrawFrame(GL10 gl) {}
+
+        private void collectImplementationVariables(ImplementationVariable[] variables) {
+            for (int i = 0; i < variables.length; i++) {
+                String name = variables[i].getName();
+                Object value = variables[i].getValue();
+                boolean dynamicArray = variables[i] instanceof DynamicIntVectorValue;
+                mParent.addImplementationVariable(name, value, dynamicArray);
+            }
+        }
     }
 }
diff --git a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/GraphicsDeviceInfo.java b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/GraphicsDeviceInfo.java
index e8283f8..f1832b3 100644
--- a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/GraphicsDeviceInfo.java
+++ b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/GraphicsDeviceInfo.java
@@ -19,6 +19,11 @@
 
 import com.android.compatibility.common.util.DeviceInfoStore;
 
+import java.io.IOException;
+
+import java.util.List;
+import java.util.Set;
+
 /**
  * Graphics device info collector.
  */
@@ -26,6 +31,34 @@
 
     private static final String LOG_TAG = "GraphicsDeviceInfo";
 
+    // Java generics won't handle basic types, can't simplify
+    private static void storeValue(DeviceInfoStore store, String name, float[] valueArray,
+                                   boolean dynamicArray) throws IOException {
+        if (valueArray.length == 1 && !dynamicArray) {
+            store.addResult(name, valueArray[0]);
+        } else {
+            store.addArrayResult(name, valueArray);
+        }
+    }
+
+    private static void storeValue(DeviceInfoStore store, String name, int[] valueArray,
+                                   boolean dynamicArray) throws IOException {
+        if (valueArray.length == 1 && !dynamicArray) {
+            store.addResult(name, valueArray[0]);
+        } else {
+            store.addArrayResult(name, valueArray);
+        }
+    }
+
+    private static void storeValue(DeviceInfoStore store, String name, long[] valueArray,
+                                   boolean dynamicArray) throws IOException {
+        if (valueArray.length == 1 && !dynamicArray) {
+            store.addResult(name, valueArray[0]);
+        } else {
+            store.addArrayResult(name, valueArray);
+        }
+    }
+
     @Override
     protected void collectDeviceInfo(DeviceInfoStore store) throws Exception {
         GlesStubActivity stubActivity = GraphicsDeviceInfo.this.launchActivity(
@@ -40,5 +73,27 @@
 
         store.addListResult("gl_texture", stubActivity.getCompressedTextureFormats());
         store.addListResult("gl_extension", stubActivity.getOpenGlExtensions());
+
+        Set<String> variables = stubActivity.getImplementationVariableNames();
+        for (String name : variables) {
+            Object value = stubActivity.getImplementationVariable(name);
+            String lowerCaseName = name.toLowerCase();
+            if (lowerCaseName.equals("gl_version")) {
+                lowerCaseName = "gl_version_real";
+            }
+
+            if (value != null) {
+                boolean dynamicArray = stubActivity.isDynamicArrayVariable(name);
+                if (value instanceof String) {
+                    store.addResult(lowerCaseName, (String)value);
+                } else if (value instanceof float[]) {
+                    storeValue(store, lowerCaseName, (float[])value, dynamicArray);
+                } else if (value instanceof long[]) {
+                    storeValue(store, lowerCaseName, (long[])value, dynamicArray);
+                } else {
+                    storeValue(store, lowerCaseName, (int[])value, dynamicArray);
+                }
+            }
+        }
     }
 }
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/command/CompatibilityConsole.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/command/CompatibilityConsole.java
index d7fa7a7..19db37a 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/command/CompatibilityConsole.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/command/CompatibilityConsole.java
@@ -339,6 +339,7 @@
                         Integer.toString(i),
                         Integer.toString(result.countResults(TestStatus.PASS)),
                         Integer.toString(result.countResults(TestStatus.FAIL)),
+                        Integer.toString(result.getNotExecuted()),
                         moduleProgress,
                         CompatibilityBuildHelper.getDirSuffix(result.getStartTime()),
                         result.getTestPlan(),
@@ -350,8 +351,9 @@
 
 
             // add the table header to the beginning of the list
-            table.add(0, Arrays.asList("Session", "Pass", "Fail", "Modules Complete", "Result Directory",
-                    "Test Plan", "Device serial(s)", "Build ID", "Product"));
+            table.add(0, Arrays.asList("Session", "Pass", "Fail", "Not Executed",
+                    "Modules Complete", "Result Directory", "Test Plan", "Device serial(s)",
+                    "Build ID", "Product"));
             tableFormatter.displayTable(table, new PrintWriter(System.out, true));
         } else {
             printLine(String.format("No results found"));
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java
index ebdc1e6..75541d9 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/result/ResultReporter.java
@@ -260,7 +260,9 @@
     public void testStarted(TestIdentifier test) {
         mCurrentCaseResult = mCurrentModuleResult.getOrCreateResult(test.getClassName());
         mCurrentResult = mCurrentCaseResult.getOrCreateResult(test.getTestName().trim());
-        mCurrentResult.reset();
+        if (mCurrentResult.isRetry()) {
+            mCurrentResult.reset(); // clear result status for this invocation
+        }
         mCurrentTestNum++;
     }
 
@@ -335,7 +337,7 @@
         mCurrentModuleResult.addRuntime(elapsedTime);
         // Expect them to be equal, but greater than to be safe.
         mCurrentModuleResult.setDone(mCurrentTestNum >= mTotalTestsInModule);
-
+        mResult.notExecuted(Math.max(mTotalTestsInModule - mCurrentTestNum, 0));
         if (isShardResultReporter()) {
             // Forward module results to the master.
             mMasterResultReporter.mergeModuleResult(mCurrentModuleResult);
@@ -440,10 +442,11 @@
         String moduleProgress = String.format("%d of %d",
                 mResult.getModuleCompleteCount(), mResult.getModules().size());
 
-        info("Invocation finished in %s. PASSED: %d, FAILED: %d, MODULES: %s",
+        info("Invocation finished in %s. PASSED: %d, FAILED: %d, NOT EXECUTED: %d, MODULES: %s",
                 TimeUtil.formatElapsedTime(elapsedTime),
                 mResult.countResults(TestStatus.PASS),
                 mResult.countResults(TestStatus.FAIL),
+                mResult.getNotExecuted(),
                 moduleProgress);
 
         long startTime = mResult.getStartTime();
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 9784220..e75131f 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
@@ -575,12 +575,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/result/ResultReporterTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ResultReporterTest.java
index cefb74f..0df6ad0 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ResultReporterTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/result/ResultReporterTest.java
@@ -155,6 +155,124 @@
                 result3.getResultStatus());
     }
 
+    private void makeTestRun(String[] methods, boolean[] passes) {
+        mReporter.testRunStarted(ID, methods.length);
+
+        for (int i = 0; i < methods.length; i++) {
+            TestIdentifier test = new TestIdentifier(CLASS, methods[i]);
+            mReporter.testStarted(test);
+            if (!passes[i]) {
+                mReporter.testFailed(test, STACK_TRACE);
+            }
+            mReporter.testEnded(test, new HashMap<String, String>());
+        }
+
+        mReporter.testRunEnded(10, new HashMap<String, String>());
+    }
+
+    public void testRepeatedExecutions() throws Exception {
+        String[] methods = new String[] {METHOD_1, METHOD_2, METHOD_3};
+
+        mReporter.invocationStarted(mBuildInfo);
+
+        makeTestRun(methods, new boolean[] {true, false, true});
+        makeTestRun(methods, new boolean[] {true, false, false});
+        makeTestRun(methods, new boolean[] {true, true, true});
+
+        mReporter.invocationEnded(10);
+
+        // Verification
+
+        IInvocationResult result = mReporter.getResult();
+        assertEquals("Expected 1 pass", 1, result.countResults(TestStatus.PASS));
+        assertEquals("Expected 2 failures", 2, result.countResults(TestStatus.FAIL));
+        List<IModuleResult> modules = result.getModules();
+        assertEquals("Expected 1 module", 1, modules.size());
+        IModuleResult module = modules.get(0);
+        assertEquals("Incorrect ID", ID, module.getId());
+        List<ICaseResult> caseResults = module.getResults();
+        assertEquals("Expected 1 test case", 1, caseResults.size());
+        ICaseResult caseResult = caseResults.get(0);
+        List<ITestResult> testResults = caseResult.getResults();
+        assertEquals("Expected 3 tests", 3, testResults.size());
+
+        // Test 1 details
+        ITestResult result1 = caseResult.getResult(METHOD_1);
+        assertNotNull(String.format("Expected result for %s", TEST_1), result1);
+        assertEquals(String.format("Expected pass for %s", TEST_1), TestStatus.PASS,
+                result1.getResultStatus());
+
+        // Test 2 details
+        ITestResult result2 = caseResult.getResult(METHOD_2);
+        assertNotNull(String.format("Expected result for %s", TEST_2), result2);
+        assertEquals(String.format("Expected fail for %s", TEST_2), TestStatus.FAIL,
+                result2.getResultStatus());
+        // TODO: Define requirement. Should this result have multiple stack traces?
+        assertEquals(result2.getStackTrace(), STACK_TRACE);
+
+        // Test 3 details
+        ITestResult result3 = caseResult.getResult(METHOD_3);
+        assertNotNull(String.format("Expected result for %s", TEST_3), result3);
+        assertEquals(String.format("Expected fail for %s", TEST_3), TestStatus.FAIL,
+                result3.getResultStatus());
+        assertEquals(result3.getStackTrace(), STACK_TRACE);
+    }
+
+    public void testRetry() throws Exception {
+        mReporter.invocationStarted(mBuildInfo);
+
+        // Set up IInvocationResult with existing results from previous session
+        mReporter.testRunStarted(ID, 2);
+        IInvocationResult invocationResult = mReporter.getResult();
+        IModuleResult moduleResult = invocationResult.getOrCreateModule(ID);
+        ICaseResult caseResult = moduleResult.getOrCreateResult(CLASS);
+        ITestResult testResult1 = caseResult.getOrCreateResult(METHOD_1);
+        testResult1.setResultStatus(TestStatus.PASS);
+        testResult1.setRetry(true);
+        ITestResult testResult2 = caseResult.getOrCreateResult(METHOD_2);
+        testResult2.setResultStatus(TestStatus.FAIL);
+        testResult2.setStackTrace(STACK_TRACE);
+        testResult2.setRetry(true);
+
+        // Flip results for the current session
+        TestIdentifier test1 = new TestIdentifier(CLASS, METHOD_1);
+        mReporter.testStarted(test1);
+        mReporter.testFailed(test1, STACK_TRACE);
+        mReporter.testEnded(test1, new HashMap<String, String>());
+        TestIdentifier test2 = new TestIdentifier(CLASS, METHOD_2);
+        mReporter.testStarted(test2);
+        mReporter.testEnded(test2, new HashMap<String, String>());
+
+        mReporter.testRunEnded(10, new HashMap<String, String>());
+        mReporter.invocationEnded(10);
+
+        // Verification that results have been overwritten.
+        IInvocationResult result = mReporter.getResult();
+        assertEquals("Expected 1 pass", 1, result.countResults(TestStatus.PASS));
+        assertEquals("Expected 1 failure", 1, result.countResults(TestStatus.FAIL));
+        List<IModuleResult> modules = result.getModules();
+        assertEquals("Expected 1 module", 1, modules.size());
+        IModuleResult module = modules.get(0);
+        List<ICaseResult> cases = module.getResults();
+        assertEquals("Expected 1 test case", 1, cases.size());
+        ICaseResult case1 = cases.get(0);
+        List<ITestResult> testResults = case1.getResults();
+        assertEquals("Expected 2 tests", 2, testResults.size());
+
+        // Test 1 details
+        ITestResult finalTestResult1 = case1.getResult(METHOD_1);
+        assertNotNull(String.format("Expected result for %s", TEST_1), finalTestResult1);
+        assertEquals(String.format("Expected fail for %s", TEST_1), TestStatus.FAIL,
+                finalTestResult1.getResultStatus());
+        assertEquals(finalTestResult1.getStackTrace(), STACK_TRACE);
+
+        // Test 2 details
+        ITestResult finalTestResult2 = case1.getResult(METHOD_2);
+        assertNotNull(String.format("Expected result for %s", TEST_2), finalTestResult2);
+        assertEquals(String.format("Expected pass for %s", TEST_2), TestStatus.PASS,
+                finalTestResult2.getResultStatus());
+    }
+
     public void testResultReporting_moduleNotDone() throws Exception {
         mReporter.invocationStarted(mBuildInfo);
         mReporter.testRunStarted(ID, 2);
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..f36a1f50a
--- /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/IInvocationResult.java b/common/util/src/com/android/compatibility/common/util/IInvocationResult.java
index c74952e..0f1a5f0 100644
--- a/common/util/src/com/android/compatibility/common/util/IInvocationResult.java
+++ b/common/util/src/com/android/compatibility/common/util/IInvocationResult.java
@@ -41,6 +41,16 @@
     int countResults(TestStatus result);
 
     /**
+     * @param the number of additional tests not executed tests for the invocation.
+     */
+    void notExecuted(int count);
+
+    /**
+     * @return the number of tests that have not been executed in this moduleResult.
+     */
+    int getNotExecuted();
+
+    /**
      * @param plan the plan associated with this result.
      */
     void setTestPlan(String plan);
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/ITestResult.java b/common/util/src/com/android/compatibility/common/util/ITestResult.java
index 918495b..c35b997 100644
--- a/common/util/src/com/android/compatibility/common/util/ITestResult.java
+++ b/common/util/src/com/android/compatibility/common/util/ITestResult.java
@@ -118,14 +118,6 @@
     void passed(ReportLog report);
 
     /**
-     * Report that the test was not executed.
-     *
-     * This means something like a loss of connection to the hardware,
-     * and indicates the run of this test was invalid and needs to be redone.
-     */
-    void notExecuted();
-
-    /**
      * Report that the test was skipped.
      *
      * This means that the test is not considered appropriate for the
@@ -140,4 +132,14 @@
      */
     void reset();
 
+    /**
+     * Sets whether the test result status has been generated from a previous testing session.
+     */
+    void setRetry(boolean isRetry);
+
+    /**
+     * Retrieves whether the test result status has been generated from a previous testing session.
+     */
+    boolean isRetry();
+
 }
diff --git a/common/util/src/com/android/compatibility/common/util/InvocationResult.java b/common/util/src/com/android/compatibility/common/util/InvocationResult.java
index b028bf1..1ff4557 100644
--- a/common/util/src/com/android/compatibility/common/util/InvocationResult.java
+++ b/common/util/src/com/android/compatibility/common/util/InvocationResult.java
@@ -36,6 +36,7 @@
     private String mBuildFingerprint;
     private String mTestPlan;
     private String mCommandLineArgs;
+    private int mNotExecuted = 0;
 
     /**
      * {@inheritDoc}
@@ -63,6 +64,22 @@
      * {@inheritDoc}
      */
     @Override
+    public void notExecuted(int count) {
+        mNotExecuted += count;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int getNotExecuted() {
+        return mNotExecuted;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
     public IModuleResult getOrCreateModule(String id) {
         IModuleResult moduleResult = mModuleResults.get(id);
         if (moduleResult == null) {
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 9fce4d8f..c95a086 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,14 @@
      * {@inheritDoc}
      */
     @Override
+    public boolean isPassed() {
+        return mDone && countResults(TestStatus.FAIL) == 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
     public String getId() {
         return mId;
     }
diff --git a/common/util/src/com/android/compatibility/common/util/ResultHandler.java b/common/util/src/com/android/compatibility/common/util/ResultHandler.java
index 799eaee..82c98da 100644
--- a/common/util/src/com/android/compatibility/common/util/ResultHandler.java
+++ b/common/util/src/com/android/compatibility/common/util/ResultHandler.java
@@ -142,6 +142,8 @@
                 parser.require(XmlPullParser.END_TAG, NS, BUILD_TAG);
                 parser.nextTag();
                 parser.require(XmlPullParser.START_TAG, NS, SUMMARY_TAG);
+                invocation.notExecuted(
+                        Integer.parseInt(parser.getAttributeValue(NS, NOT_EXECUTED_ATTR)));
                 parser.nextTag();
                 parser.require(XmlPullParser.END_TAG, NS, SUMMARY_TAG);
                 while (parser.nextTag() == XmlPullParser.START_TAG) {
@@ -162,7 +164,8 @@
                             ITestResult test = testCase.getOrCreateResult(testName);
                             String result = parser.getAttributeValue(NS, RESULT_ATTR);
                             test.setResultStatus(TestStatus.getStatus(result));
-                            if (parser.nextTag() == XmlPullParser.START_TAG) {
+                            test.setRetry(true);
+                            while (parser.nextTag() == XmlPullParser.START_TAG) {
                                 if (parser.getName().equals(FAILURE_TAG)) {
                                     test.setMessage(parser.getAttributeValue(NS, MESSAGE_ATTR));
                                     if (parser.nextTag() == XmlPullParser.START_TAG) {
@@ -172,19 +175,17 @@
                                         parser.nextTag();
                                     }
                                     parser.require(XmlPullParser.END_TAG, NS, FAILURE_TAG);
-                                    parser.nextTag();
                                 } else if (parser.getName().equals(BUGREPORT_TAG)) {
                                     test.setBugReport(parser.nextText());
-                                    parser.nextTag();
+                                    parser.require(XmlPullParser.END_TAG, NS, BUGREPORT_TAG);
                                 } else if (parser.getName().equals(LOGCAT_TAG)) {
                                     test.setLog(parser.nextText());
-                                    parser.nextTag();
+                                    parser.require(XmlPullParser.END_TAG, NS, LOGCAT_TAG);
                                 } else if (parser.getName().equals(SCREENSHOT_TAG)) {
                                     test.setScreenshot(parser.nextText());
-                                    parser.nextTag();
+                                    parser.require(XmlPullParser.END_TAG, NS, SCREENSHOT_TAG);
                                 } else {
                                     test.setReportLog(ReportLog.parse(parser));
-                                    parser.nextTag();
                                 }
                             }
                             parser.require(XmlPullParser.END_TAG, NS, TEST_TAG);
@@ -230,7 +231,7 @@
                     throws IOException, XmlPullParserException {
         int passed = result.countResults(TestStatus.PASS);
         int failed = result.countResults(TestStatus.FAIL);
-        int notExecuted = result.countResults(TestStatus.NOT_EXECUTED);
+        int notExecuted = result.getNotExecuted();
         File resultFile = new File(resultDir, TEST_RESULT_FILE_NAME);
         OutputStream stream = new FileOutputStream(resultFile);
         XmlSerializer serializer = XmlPullParserFactory.newInstance(TYPE, null).newSerializer();
@@ -315,8 +316,12 @@
                 serializer.startTag(NS, CASE_TAG);
                 serializer.attribute(NS, NAME_ATTR, cr.getName());
                 for (ITestResult r : cr.getResults()) {
+                    TestStatus status = r.getResultStatus();
+                    if (status == null) {
+                        continue; // test was not executed, don't report
+                    }
                     serializer.startTag(NS, TEST_TAG);
-                    serializer.attribute(NS, RESULT_ATTR, r.getResultStatus().getValue());
+                    serializer.attribute(NS, RESULT_ATTR, status.getValue());
                     serializer.attribute(NS, NAME_ATTR, r.getName());
                     String message = r.getMessage();
                     if (message != null) {
diff --git a/common/util/src/com/android/compatibility/common/util/TestResult.java b/common/util/src/com/android/compatibility/common/util/TestResult.java
index d984a84..ba378d0 100644
--- a/common/util/src/com/android/compatibility/common/util/TestResult.java
+++ b/common/util/src/com/android/compatibility/common/util/TestResult.java
@@ -29,6 +29,7 @@
     private String mBugReport;
     private String mLog;
     private String mScreenshot;
+    private boolean mIsRetry;
 
     /**
      * Create a {@link TestResult} for the given test name.
@@ -200,14 +201,6 @@
      * {@inheritDoc}
      */
     @Override
-    public void notExecuted() {
-        setResultStatus(TestStatus.NOT_EXECUTED);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
     public void skipped() {
         // TODO(b/28386054): Report SKIPPED as a separate result.
         // For now, we mark this as PASS.
@@ -226,6 +219,23 @@
         mBugReport = null;
         mLog = null;
         mScreenshot = null;
+        mIsRetry = false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setRetry(boolean isRetry) {
+        mIsRetry = isRetry;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean isRetry() {
+        return mIsRetry;
     }
 
     /**
diff --git a/common/util/src/com/android/compatibility/common/util/TestStatus.java b/common/util/src/com/android/compatibility/common/util/TestStatus.java
index 766e4d2..744f760 100644
--- a/common/util/src/com/android/compatibility/common/util/TestStatus.java
+++ b/common/util/src/com/android/compatibility/common/util/TestStatus.java
@@ -20,8 +20,7 @@
  */
 public enum TestStatus {
     PASS("pass"),
-    FAIL("fail"),
-    NOT_EXECUTED("not_executed");
+    FAIL("fail");
 
     private String mValue;
 
@@ -45,7 +44,7 @@
      * @param value
      * @return the {@link TestStatus} or <code>null</code> if it could not be found
      */
-    static TestStatus getStatus(String  value) {
+    static TestStatus getStatus(String value) {
         for (TestStatus status : TestStatus.values()) {
             if (value.compareToIgnoreCase(status.getValue()) == 0) {
                 return status;
diff --git a/common/util/tests/src/com/android/compatibility/common/util/ResultHandlerTest.java b/common/util/tests/src/com/android/compatibility/common/util/ResultHandlerTest.java
index cf958c7..a3b2670 100644
--- a/common/util/tests/src/com/android/compatibility/common/util/ResultHandlerTest.java
+++ b/common/util/tests/src/com/android/compatibility/common/util/ResultHandlerTest.java
@@ -74,6 +74,9 @@
     private static final String MESSAGE = "Something small is not alright";
     private static final String STACK_TRACE = "Something small is not alright\n " +
             "at four.big.insects.Marley.sing(Marley.java:10)";
+    private static final String BUG_REPORT = "https://cnsviewer.corp.google.com/cns/bugreport.txt";
+    private static final String LOGCAT = "https://cnsviewer.corp.google.com/cns/logcat.gz";
+    private static final String SCREENSHOT = "https://cnsviewer.corp.google.com/screenshot.png";
     private static final long START_MS = 1431586801000L;
     private static final long END_MS = 1431673199000L;
     private static final String START_DISPLAY = "Fri Aug 20 15:13:03 PDT 2010";
@@ -118,6 +121,9 @@
             "        <Failure message=\"%s\">\n" +
             "          <StackTrace>%s</StackTrace>\n" +
             "        </Failure>\n" +
+            "        <BugReport>%s</BugReport>\n" +
+            "        <Logcat>%s</Logcat>\n" +
+            "        <Screenshot>%s</Screenshot>\n" +
             "      </Test>\n";
     private static final String XML_TEST_RESULT =
             "      <Test result=\"pass\" name=\"%s\">\n" +
@@ -156,7 +162,8 @@
         ITestResult moduleATest1 = moduleACase.getOrCreateResult(METHOD_1);
         moduleATest1.setResultStatus(TestStatus.PASS);
         ITestResult moduleATest2 = moduleACase.getOrCreateResult(METHOD_2);
-        moduleATest2.setResultStatus(TestStatus.NOT_EXECUTED);
+        moduleATest2.setResultStatus(null); // not executed test
+        result.notExecuted(1);
 
         IModuleResult moduleB = result.getOrCreateModule(ID_B);
         ICaseResult moduleBCase = moduleB.getOrCreateResult(CLASS_B);
@@ -164,6 +171,9 @@
         moduleBTest3.setResultStatus(TestStatus.FAIL);
         moduleBTest3.setMessage(MESSAGE);
         moduleBTest3.setStackTrace(STACK_TRACE);
+        moduleBTest3.setBugReport(BUG_REPORT);
+        moduleBTest3.setLog(LOGCAT);
+        moduleBTest3.setScreenshot(SCREENSHOT);
         ITestResult moduleBTest4 = moduleBCase.getOrCreateResult(METHOD_4);
         moduleBTest4.setResultStatus(TestStatus.PASS);
         ReportLog report = new ReportLog();
@@ -193,12 +203,11 @@
             String buildInfo = String.format(XML_BUILD_INFO, DEVICE_A,
                     EXAMPLE_BUILD_ID, EXAMPLE_BUILD_PRODUCT);
             String summary = String.format(XML_SUMMARY, 2, 1, 1);
-            String moduleATest1 = String.format(XML_TEST_PASS, METHOD_1);
-            String moduleATest2 = String.format(XML_TEST_NOT_EXECUTED, METHOD_2);
-            String moduleATests = String.format(JOIN, moduleATest1, moduleATest2);
-            String moduleACases = String.format(XML_CASE, CLASS_A, moduleATests);
+            String moduleATest = String.format(XML_TEST_PASS, METHOD_1);
+            String moduleACases = String.format(XML_CASE, CLASS_A, moduleATest);
             String moduleA = String.format(XML_MODULE, NAME_A, ABI, DEVICE_A, moduleACases);
-            String moduleBTest3 = String.format(XML_TEST_FAIL, METHOD_3, MESSAGE, STACK_TRACE);
+            String moduleBTest3 = String.format(XML_TEST_FAIL, METHOD_3, MESSAGE, STACK_TRACE,
+                    BUG_REPORT, LOGCAT, SCREENSHOT);
             String moduleBTest4 = String.format(XML_TEST_RESULT, METHOD_4,
                     SUMMARY_SOURCE, SUMMARY_MESSAGE, ResultType.HIGHER_BETTER.toReportString(),
                     ResultUnit.SCORE.toReportString(), Double.toString(SUMMARY_VALUE),
@@ -235,7 +244,7 @@
         IInvocationResult result = results.get(0);
         assertEquals("Expected 2 passes", 2, result.countResults(TestStatus.PASS));
         assertEquals("Expected 1 failure", 1, result.countResults(TestStatus.FAIL));
-        assertEquals("Expected 1 not executed", 1, result.countResults(TestStatus.NOT_EXECUTED));
+        assertEquals("Expected 1 not executed", 1, result.getNotExecuted());
 
         Map<String, String> buildInfo = result.getInvocationInfo();
         assertEquals("Incorrect Build ID", EXAMPLE_BUILD_ID, buildInfo.get(BUILD_ID));
@@ -256,7 +265,6 @@
         IModuleResult moduleA = modules.get(0);
         assertEquals("Expected 1 pass", 1, moduleA.countResults(TestStatus.PASS));
         assertEquals("Expected 0 failures", 0, moduleA.countResults(TestStatus.FAIL));
-        assertEquals("Expected 1 not executed", 1, moduleA.countResults(TestStatus.NOT_EXECUTED));
         assertEquals("Incorrect ABI", ABI, moduleA.getAbi());
         assertEquals("Incorrect name", NAME_A, moduleA.getName());
         assertEquals("Incorrect ID", ID_A, moduleA.getId());
@@ -265,7 +273,7 @@
         ICaseResult moduleACase = moduleACases.get(0);
         assertEquals("Incorrect name", CLASS_A, moduleACase.getName());
         List<ITestResult> moduleAResults = moduleACase.getResults();
-        assertEquals("Expected 2 results", 2, moduleAResults.size());
+        assertEquals("Expected 1 result", 1, moduleAResults.size());
         ITestResult moduleATest1 = moduleAResults.get(0);
         assertEquals("Incorrect name", METHOD_1, moduleATest1.getName());
         assertEquals("Incorrect result", TestStatus.PASS, moduleATest1.getResultStatus());
@@ -275,20 +283,10 @@
         assertNull("Unexpected message", moduleATest1.getMessage());
         assertNull("Unexpected stack trace", moduleATest1.getStackTrace());
         assertNull("Unexpected report", moduleATest1.getReportLog());
-        ITestResult moduleATest2 = moduleAResults.get(1);
-        assertEquals("Incorrect name", METHOD_2, moduleATest2.getName());
-        assertEquals("Incorrect result", TestStatus.NOT_EXECUTED, moduleATest2.getResultStatus());
-        assertNull("Unexpected bugreport", moduleATest2.getBugReport());
-        assertNull("Unexpected log", moduleATest2.getLog());
-        assertNull("Unexpected screenshot", moduleATest2.getScreenshot());
-        assertNull("Unexpected message", moduleATest2.getMessage());
-        assertNull("Unexpected stack trace", moduleATest2.getStackTrace());
-        assertNull("Unexpected report", moduleATest2.getReportLog());
 
         IModuleResult moduleB = modules.get(1);
         assertEquals("Expected 1 pass", 1, moduleB.countResults(TestStatus.PASS));
         assertEquals("Expected 1 failure", 1, moduleB.countResults(TestStatus.FAIL));
-        assertEquals("Expected 0 not executed", 0, moduleB.countResults(TestStatus.NOT_EXECUTED));
         assertEquals("Incorrect ABI", ABI, moduleB.getAbi());
         assertEquals("Incorrect name", NAME_B, moduleB.getName());
         assertEquals("Incorrect ID", ID_B, moduleB.getId());
@@ -301,9 +299,9 @@
         ITestResult moduleBTest3 = moduleBResults.get(0);
         assertEquals("Incorrect name", METHOD_3, moduleBTest3.getName());
         assertEquals("Incorrect result", TestStatus.FAIL, moduleBTest3.getResultStatus());
-        assertNull("Unexpected bugreport", moduleBTest3.getBugReport());
-        assertNull("Unexpected log", moduleBTest3.getLog());
-        assertNull("Unexpected screenshot", moduleBTest3.getScreenshot());
+        assertEquals("Incorrect bugreport", BUG_REPORT, moduleBTest3.getBugReport());
+        assertEquals("Incorrect log", LOGCAT, moduleBTest3.getLog());
+        assertEquals("Incorrect screenshot", SCREENSHOT, moduleBTest3.getScreenshot());
         assertEquals("Incorrect message", MESSAGE, moduleBTest3.getMessage());
         assertEquals("Incorrect stack trace", STACK_TRACE, moduleBTest3.getStackTrace());
         assertNull("Unexpected report", moduleBTest3.getReportLog());
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..8b62604a 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..ab643a0 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;
@@ -283,60 +295,75 @@
      * Asserts whether the active network is available or not.
      */
     private void assertNetworkAccess(boolean expectAvailable) throws Exception {
-        final Intent intent = new Intent(ACTION_CHECK_NETWORK);
-
         final int maxTries = 5;
-        String resultData = null;
+        String error = null;
+        int timeoutMs = 500;
+
         for (int i = 1; i <= maxTries; i++) {
-            resultData = sendOrderedBroadcast(intent);
-            assertNotNull("timeout waiting for ordered broadcast", resultData);
+            error = checkNetworkAccess(expectAvailable);
 
-            // Network status format is described on MyBroadcastReceiver.checkNetworkStatus()
-            final String[] parts = resultData.split(NETWORK_STATUS_SEPARATOR);
-            assertEquals("Wrong network status: " + resultData, 5, parts.length); // Sanity check
-            final State state = State.valueOf(parts[0]);
-            final DetailedState detailedState = DetailedState.valueOf(parts[1]);
-            final boolean connected = Boolean.valueOf(parts[2]);
-            final String connectionCheckDetails = parts[3];
-            final String networkInfo = parts[4];
+            if (error.isEmpty()) return;
 
-            if (expectAvailable) {
-                if (!connected) {
-                    // Since it's establishing a connection to an external site, it could be flaky.
-                    Log.w(TAG, "Failed to connect to an external site on attempt #" + i +
-                            " (error: " + connectionCheckDetails + ", NetworkInfo: " + networkInfo
-                            + "); sleeping " + NETWORK_TIMEOUT_MS + "ms before trying again");
-                    SystemClock.sleep(NETWORK_TIMEOUT_MS);
-                    continue;
-                }
-                if (state != State.CONNECTED) {
-                    Log.d(TAG, "State (" + state + ") not set to CONNECTED on attempt #" + i
-                            + "; sleeping 1s before trying again");
-                    SystemClock.sleep(SECOND_IN_MS);
-                } else {
-                    assertEquals("wrong detailed state for " + networkInfo,
-                            DetailedState.CONNECTED, detailedState);
-                    return;
-                }
-                return;
-            } else {
-                assertFalse("should not be connected: " + connectionCheckDetails
-                        + " (network info: " + networkInfo + ")", connected);
-                if (state != State.DISCONNECTED) {
-                    // When the network info state change, it's possible the app still get the
-                    // previous value, so we need to retry a couple times.
-                    Log.d(TAG, "State (" + state + ") not set to DISCONNECTED on attempt #" + i
-                            + "; sleeping 1s before trying again");
-                    SystemClock.sleep(SECOND_IN_MS);
-                } else {
-                    assertEquals("wrong detailed state for " + networkInfo,
-                            DetailedState.BLOCKED, detailedState);
-                   return;
-                }
-            }
+            // TODO: ideally, it should retry only when it cannot connect to an external site,
+            // or no retry at all! But, currently, the initial change fails almost always on
+            // battery saver tests because the netd changes are made asynchronously.
+            // Once b/27803922 is fixed, this retry mechanism should be revisited.
+
+            Log.w(TAG, "Network status didn't match for expectAvailable=" + expectAvailable
+                    + " on attempt #" + i + ": " + error + "\n"
+                    + "Sleeping " + timeoutMs + "ms before trying again");
+            SystemClock.sleep(timeoutMs);
+            // Exponential back-off.
+            timeoutMs = Math.min(timeoutMs*2, NETWORK_TIMEOUT_MS);
         }
         fail("Invalid state for expectAvailable=" + expectAvailable + " after " + maxTries
-                + " attempts. Last data: " + resultData);
+                + " attempts.\nLast error: " + error);
+    }
+
+    /**
+     * Checks whether the network is available as expected.
+     *
+     * @return error message with the mismatch (or empty if assertion passed).
+     */
+    private String checkNetworkAccess(boolean expectAvailable) throws Exception {
+        String resultData = sendOrderedBroadcast(new Intent(ACTION_CHECK_NETWORK));
+        if (resultData == null) {
+            return "timeout waiting for ordered broadcast";
+        }
+        // Network status format is described on MyBroadcastReceiver.checkNetworkStatus()
+        final String[] parts = resultData.split(NETWORK_STATUS_SEPARATOR);
+        assertEquals("Wrong network status: " + resultData, 5, parts.length); // Sanity check
+        final State state = State.valueOf(parts[0]);
+        final DetailedState detailedState = DetailedState.valueOf(parts[1]);
+        final boolean connected = Boolean.valueOf(parts[2]);
+        final String connectionCheckDetails = parts[3];
+        final String networkInfo = parts[4];
+
+        final StringBuilder errors = new StringBuilder();
+        final State expectedState;
+        final DetailedState expectedDetailedState;
+        if (expectAvailable) {
+            expectedState = State.CONNECTED;
+            expectedDetailedState = DetailedState.CONNECTED;
+        } else {
+            expectedState = State.DISCONNECTED;
+            expectedDetailedState = DetailedState.BLOCKED;
+        }
+
+        if (expectAvailable != connected) {
+            errors.append(String.format("External site connection failed: expected %s, got %s\n",
+                    expectAvailable, connected));
+        }
+        if (expectedState != state || expectedDetailedState != detailedState) {
+            errors.append(String.format("Connection state mismatch: expected %s/%s, got %s/%s\n",
+                    expectedState, expectedDetailedState, state, detailedState));
+        }
+
+        if (errors.length() > 0) {
+            errors.append("\tnetworkInfo: " + networkInfo + "\n");
+            errors.append("\tconnectionCheckDetails: " + connectionCheckDetails + "\n");
+        }
+        return errors.toString();
     }
 
     protected String executeShellCommand(String command) throws Exception {
@@ -735,10 +762,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..6d01b15 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;
@@ -57,7 +66,7 @@
  */
 public class MyBroadcastReceiver extends BroadcastReceiver {
 
-    private static final int NETWORK_TIMEOUT_MS = 15 * 1000;
+    private static final int NETWORK_TIMEOUT_MS = 5 * 1000;
 
     private final String mName;
 
@@ -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..bb1f8f3 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"
         />
@@ -54,7 +54,7 @@
                 android:resizeableActivity="true"
                 android:exported="true"
         />
-        <activity android:name=".LaunchToSideActivity"
+        <activity android:name=".LaunchingActivity"
                 android:resizeableActivity="true"
                 android:exported="true"
                 android:taskAffinity="nobody.but.LaunchToSideActivity"
@@ -159,6 +159,19 @@
             android:exported="true"
             android:launchMode="singleInstance"
         />
+        <activity android:name=".TrampolineActivity"
+                  android:exported="true"
+                  android:theme="@android:style/Theme.NoDisplay"
+        />
+        <activity android:name=".BroadcastReceiverActivity"
+                  android:resizeableActivity="true"
+                  android:exported="true"
+        />
+        <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/AbstractLifecycleLogActivity.java b/hostsidetests/services/activitymanager/app/src/android/server/app/AbstractLifecycleLogActivity.java
index e3026c9..bb54bc4 100644
--- a/hostsidetests/services/activitymanager/app/src/android/server/app/AbstractLifecycleLogActivity.java
+++ b/hostsidetests/services/activitymanager/app/src/android/server/app/AbstractLifecycleLogActivity.java
@@ -18,8 +18,12 @@
 
 import android.app.Activity;
 import android.content.res.Configuration;
+import android.graphics.Point;
 import android.os.Bundle;
+import android.util.DisplayMetrics;
 import android.util.Log;
+import android.view.Display;
+import android.view.WindowManager;
 
 public abstract class AbstractLifecycleLogActivity extends Activity {
     @Override
@@ -41,4 +45,24 @@
     }
 
     protected abstract String getTag();
+
+    protected void dumpDisplaySize(Configuration config) {
+        // Dump the display size as seen by this Activity.
+        final WindowManager wm = getSystemService(WindowManager.class);
+        final Display display = wm.getDefaultDisplay();
+        final Point point = new Point();
+        display.getSize(point);
+        final DisplayMetrics metrics = getResources().getDisplayMetrics();
+
+        final String line = "config" +
+                " size=" + buildCoordString(config.screenWidthDp, config.screenHeightDp) +
+                " displaySize=" + buildCoordString(point.x, point.y) +
+                " metricsSize=" + buildCoordString(metrics.widthPixels, metrics.heightPixels);
+
+        Log.i(getTag(), line);
+    }
+
+    protected static String buildCoordString(int x, int y) {
+        return "(" + x + "," + y + ")";
+    }
 }
diff --git a/hostsidetests/services/activitymanager/app/src/android/server/app/BroadcastReceiverActivity.java b/hostsidetests/services/activitymanager/app/src/android/server/app/BroadcastReceiverActivity.java
new file mode 100644
index 0000000..d55fea0
--- /dev/null
+++ b/hostsidetests/services/activitymanager/app/src/android/server/app/BroadcastReceiverActivity.java
@@ -0,0 +1,63 @@
+/*
+ * 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.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+
+/**
+ * Activity that registers broadcast receiver .
+ */
+public class BroadcastReceiverActivity extends Activity {
+
+    public static final String ACTION_TRIGGER_BROADCAST = "trigger_broadcast";
+
+    private TestBroadcastReceiver mBroadcastReceiver = new TestBroadcastReceiver();
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        IntentFilter broadcastFilter = new IntentFilter(ACTION_TRIGGER_BROADCAST);
+
+        registerReceiver(mBroadcastReceiver, broadcastFilter);
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+        unregisterReceiver(mBroadcastReceiver);
+    }
+
+    public class TestBroadcastReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final Bundle extras = intent.getExtras();
+            if (extras == null) {
+                return;
+            }
+            if (extras.getBoolean("finish")) {
+                finish();
+            }
+        }
+    }
+}
diff --git a/hostsidetests/services/activitymanager/app/src/android/server/app/LaunchToSideActivity.java b/hostsidetests/services/activitymanager/app/src/android/server/app/LaunchToSideActivity.java
deleted file mode 100644
index 6cdcbde..0000000
--- a/hostsidetests/services/activitymanager/app/src/android/server/app/LaunchToSideActivity.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package android.server.app;
-
-import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT;
-import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.content.ComponentName;
-import android.net.Uri;
-import android.os.Bundle;
-
-public class LaunchToSideActivity extends Activity {
-    @Override
-    protected void onNewIntent(Intent intent) {
-        super.onNewIntent(intent);
-        final Bundle extras = intent.getExtras();
-        if (extras != null && extras.getBoolean("launch_to_the_side")) {
-            Intent newIntent = new Intent();
-            String targetActivity = extras.getString("target_activity");
-            if (targetActivity != null) {
-                String packageName = getApplicationContext().getPackageName();
-                newIntent.setComponent(new ComponentName(packageName,
-                        packageName + "." + targetActivity));
-            } else {
-                newIntent.setClass(this, TestActivity.class);
-            }
-            newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_LAUNCH_ADJACENT);
-            if (extras.getBoolean("multiple_task")) {
-                newIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
-            }
-            if (extras.getBoolean("random_data")) {
-                Uri data = new Uri.Builder()
-                        .path(String.valueOf(System.currentTimeMillis()))
-                        .build();
-                newIntent.setData(data);
-            }
-            startActivity(newIntent);
-        }
-    }
-}
diff --git a/hostsidetests/services/activitymanager/app/src/android/server/app/LaunchingActivity.java b/hostsidetests/services/activitymanager/app/src/android/server/app/LaunchingActivity.java
new file mode 100644
index 0000000..83ebf09
--- /dev/null
+++ b/hostsidetests/services/activitymanager/app/src/android/server/app/LaunchingActivity.java
@@ -0,0 +1,52 @@
+package android.server.app;
+
+import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.ComponentName;
+import android.net.Uri;
+import android.os.Bundle;
+
+/**
+ * Activity that launches another activities when new intent is received.
+ */
+public class LaunchingActivity extends Activity {
+    @Override
+    protected void onNewIntent(Intent intent) {
+        super.onNewIntent(intent);
+        final Bundle extras = intent.getExtras();
+        if (extras == null) {
+            return;
+        }
+
+        Intent newIntent = new Intent();
+        String targetActivity = extras.getString("target_activity");
+        if (targetActivity != null) {
+            String packageName = getApplicationContext().getPackageName();
+            newIntent.setComponent(new ComponentName(packageName,
+                    packageName + "." + targetActivity));
+        } else {
+            newIntent.setClass(this, TestActivity.class);
+        }
+
+        if (extras.getBoolean("launch_to_the_side")) {
+            newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_LAUNCH_ADJACENT);
+            if (extras.getBoolean("multiple_task")) {
+                newIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+            }
+            if (extras.getBoolean("random_data")) {
+                Uri data = new Uri.Builder()
+                        .path(String.valueOf(System.currentTimeMillis()))
+                        .build();
+                newIntent.setData(data);
+            }
+        } else {
+            // We're all set, just launch.
+        }
+
+        startActivity(newIntent);
+    }
+}
diff --git a/hostsidetests/services/activitymanager/app/src/android/server/app/ResizeableActivity.java b/hostsidetests/services/activitymanager/app/src/android/server/app/ResizeableActivity.java
index 747bfc1..4d46a83 100644
--- a/hostsidetests/services/activitymanager/app/src/android/server/app/ResizeableActivity.java
+++ b/hostsidetests/services/activitymanager/app/src/android/server/app/ResizeableActivity.java
@@ -16,12 +16,7 @@
 package android.server.app;
 
 import android.content.res.Configuration;
-import android.graphics.Point;
 import android.os.Bundle;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.Display;
-import android.view.WindowManager;
 
 public class ResizeableActivity extends AbstractLifecycleLogActivity {
     @Override
@@ -40,24 +35,4 @@
         super.onConfigurationChanged(newConfig);
         dumpDisplaySize(newConfig);
     }
-
-    private void dumpDisplaySize(Configuration config) {
-        // Dump the display size as seen by this Activity.
-        final WindowManager wm = getSystemService(WindowManager.class);
-        final Display display = wm.getDefaultDisplay();
-        final Point point = new Point();
-        display.getSize(point);
-        final DisplayMetrics metrics = getResources().getDisplayMetrics();
-
-        final String line = "config" +
-                " size=" + buildCoordString(config.screenWidthDp, config.screenHeightDp) +
-                " displaySize=" + buildCoordString(point.x, point.y) +
-                " metricsSize=" + buildCoordString(metrics.widthPixels, metrics.heightPixels);
-
-        Log.i(getTag(), line);
-    }
-
-    private static String buildCoordString(int x, int y) {
-        return "(" + x + "," + y + ")";
-    }
 }
diff --git a/hostsidetests/services/activitymanager/app/src/android/server/app/TestActivity.java b/hostsidetests/services/activitymanager/app/src/android/server/app/TestActivity.java
index a8e51d5..f469a1c 100644
--- a/hostsidetests/services/activitymanager/app/src/android/server/app/TestActivity.java
+++ b/hostsidetests/services/activitymanager/app/src/android/server/app/TestActivity.java
@@ -16,11 +16,25 @@
 
 package android.server.app;
 
+import android.content.res.Configuration;
+
 public class TestActivity extends AbstractLifecycleLogActivity {
 
     private static final String TAG = TestActivity.class.getSimpleName();
 
     @Override
+    protected void onResume() {
+        super.onResume();
+        dumpDisplaySize(getResources().getConfiguration());
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        dumpDisplaySize(newConfig);
+    }
+
+    @Override
     protected String getTag() {
         return TAG;
     }
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/ActivityManagerActivityVisiblityTests.java b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerActivityVisiblityTests.java
index 6f527ed..fd2bb75 100644
--- a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerActivityVisiblityTests.java
+++ b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerActivityVisiblityTests.java
@@ -29,6 +29,7 @@
     private static final String TRANSLUCENT_ACTIVITY_NAME = "TranslucentActivity";
     private static final String DOCKED_ACTIVITY_NAME = "DockedActivity";
     private static final String TURN_SCREEN_ON_ACTIVITY_NAME = "TurnScreenOnActivity";
+    private static final String BROADCAST_RECEIVER_ACTIVITY = "BroadcastReceiverActivity";
 
     public void testVisibleBehindHomeActivity() throws Exception {
         executeShellCommand(getAmStartCmd(VISIBLE_BEHIND_ACTIVITY));
@@ -91,7 +92,6 @@
         executeShellCommand(getAmStartCmd(TRANSLUCENT_ACTIVITY));
 
         mAmWmState.computeState(mDevice, new String[]{TRANSLUCENT_ACTIVITY});
-        mAmWmState.assertSanity();
         mAmWmState.assertFrontStack(
                 "Fullscreen stack must be the front stack.", FULLSCREEN_WORKSPACE_STACK_ID);
         mAmWmState.assertVisibility(TRANSLUCENT_ACTIVITY, true);
@@ -114,7 +114,6 @@
         executeShellCommand(AM_MOVE_TOP_ACTIVITY_TO_PINNED_STACK_COMMAND);
 
         mAmWmState.computeState(mDevice, new String[]{TRANSLUCENT_ACTIVITY});
-        mAmWmState.assertSanity();
 
         mAmWmState.assertVisibility(TRANSLUCENT_ACTIVITY, true);
         mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, false);
@@ -139,7 +138,24 @@
         lockDevice();
         executeShellCommand(getAmStartCmd(TURN_SCREEN_ON_ACTIVITY_NAME));
         mAmWmState.computeState(mDevice, new String[] { TURN_SCREEN_ON_ACTIVITY_NAME });
-        mAmWmState.assertSanity();
         mAmWmState.assertVisibility(TURN_SCREEN_ON_ACTIVITY_NAME, true);
     }
+
+    public void testFinishActivityInNonFocusedStack() throws Exception {
+        // Launch two activities in docked stack.
+        launchActivityInDockStack(LAUNCHING_ACTIVITY);
+        launchActivity(false /* toSide */, false /* randomData */, false /* multipleTaskFlag */,
+                BROADCAST_RECEIVER_ACTIVITY);
+        mAmWmState.computeState(mDevice, new String[] { BROADCAST_RECEIVER_ACTIVITY });
+        mAmWmState.assertVisibility(BROADCAST_RECEIVER_ACTIVITY, true);
+        // Launch something to fullscreen stack to make it focused.
+        launchActivityInStack(TEST_ACTIVITY_NAME, 1);
+        mAmWmState.computeState(mDevice, new String[] { TEST_ACTIVITY_NAME });
+        mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, true);
+        // Finish activity in non-focused (docked) stack.
+        executeShellCommand("am broadcast -a trigger_broadcast --ez finish true");
+        mAmWmState.computeState(mDevice, new String[] { LAUNCHING_ACTIVITY });
+        mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true);
+        mAmWmState.assertVisibility(BROADCAST_RECEIVER_ACTIVITY, false);
+    }
 }
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/ActivityManagerAppConfigurationTests.java b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerAppConfigurationTests.java
index d409252..5b21ee9 100644
--- a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerAppConfigurationTests.java
+++ b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerAppConfigurationTests.java
@@ -15,8 +15,13 @@
  */
 package android.server.cts;
 
+import java.awt.Rectangle;
+import java.util.ArrayList;
+import java.util.List;
+
 public class ActivityManagerAppConfigurationTests extends ActivityManagerTestBase {
-    private static final String TEST_ACTIVITY_NAME = "ResizeableActivity";
+    private static final String RESIZEABLE_ACTIVITY_NAME = "ResizeableActivity";
+    private static final String TEST_ACTIVITY_NAME = "TestActivity";
 
     /**
      * Tests that the WindowManager#getDefaultDisplay() and the Configuration of the Activity
@@ -28,12 +33,12 @@
      * docked state.
      */
     public void testConfigurationUpdatesWhenResizedFromFullscreen() throws Exception {
-        launchActivityInStack(TEST_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID);
-        final ReportedSizes fullscreenSizes = getActivityDisplaySize(TEST_ACTIVITY_NAME,
+        launchActivityInStack(RESIZEABLE_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID);
+        final ReportedSizes fullscreenSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
                 FULLSCREEN_WORKSPACE_STACK_ID);
 
-        moveActivityToStack(TEST_ACTIVITY_NAME, DOCKED_STACK_ID);
-        final ReportedSizes dockedSizes = getActivityDisplaySize(TEST_ACTIVITY_NAME,
+        moveActivityToStack(RESIZEABLE_ACTIVITY_NAME, DOCKED_STACK_ID);
+        final ReportedSizes dockedSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
                 DOCKED_STACK_ID);
 
         assertSizesAreSane(fullscreenSizes, dockedSizes);
@@ -44,12 +49,12 @@
      * from docked state to fullscreen (reverse).
      */
     public void testConfigurationUpdatesWhenResizedFromDockedStack() throws Exception {
-        launchActivityInStack(TEST_ACTIVITY_NAME, DOCKED_STACK_ID);
-        final ReportedSizes dockedSizes = getActivityDisplaySize(TEST_ACTIVITY_NAME,
+        launchActivityInStack(RESIZEABLE_ACTIVITY_NAME, DOCKED_STACK_ID);
+        final ReportedSizes dockedSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
                 DOCKED_STACK_ID);
 
-        moveActivityToStack(TEST_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID);
-        final ReportedSizes fullscreenSizes = getActivityDisplaySize(TEST_ACTIVITY_NAME,
+        moveActivityToStack(RESIZEABLE_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID);
+        final ReportedSizes fullscreenSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
                 FULLSCREEN_WORKSPACE_STACK_ID);
 
         assertSizesAreSane(fullscreenSizes, dockedSizes);
@@ -60,34 +65,130 @@
      */
     public void testConfigurationUpdatesWhenRotatingWhileFullscreen() throws Exception {
         setDeviceRotation(0);
-        launchActivityInStack(TEST_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID);
-        final ReportedSizes orientationASizes = getActivityDisplaySize(TEST_ACTIVITY_NAME,
+        launchActivityInStack(RESIZEABLE_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID);
+        final ReportedSizes orientationASizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
                 FULLSCREEN_WORKSPACE_STACK_ID);
 
         setDeviceRotation(1);
-        final ReportedSizes orientationBSizes = getActivityDisplaySize(TEST_ACTIVITY_NAME,
+        final ReportedSizes orientationBSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
                 FULLSCREEN_WORKSPACE_STACK_ID);
         assertSizesRotate(orientationASizes, orientationBSizes);
     }
 
-
     /**
      * Same as {@link #testConfigurationUpdatesWhenRotatingWhileFullscreen()} but when the Activity
      * is in the docked stack.
      */
     public void testConfigurationUpdatesWhenRotatingWhileDocked() throws Exception {
         setDeviceRotation(0);
-        launchActivityInStack(TEST_ACTIVITY_NAME, DOCKED_STACK_ID);
-        final ReportedSizes orientationASizes = getActivityDisplaySize(TEST_ACTIVITY_NAME,
+        launchActivityInStack(RESIZEABLE_ACTIVITY_NAME, DOCKED_STACK_ID);
+        final ReportedSizes orientationASizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
                 DOCKED_STACK_ID);
 
         setDeviceRotation(1);
-        final ReportedSizes orientationBSizes = getActivityDisplaySize(TEST_ACTIVITY_NAME,
+        final ReportedSizes orientationBSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
                 DOCKED_STACK_ID);
         assertSizesRotate(orientationASizes, orientationBSizes);
     }
 
     /**
+     * Tests when activity moved from fullscreen stack to docked and back. Activity will be
+     * relaunched twice and it should have same config as initial one.
+     */
+    public void testSameConfigurationFullSplitFullRelaunch() throws Exception {
+        moveActivityFullSplitFull(TEST_ACTIVITY_NAME);
+    }
+
+    /**
+     * Same as {@link #testSameConfigurationFullSplitFullRelaunch} but without relaunch.
+     */
+    public void testSameConfigurationFullSplitFullNoRelaunch() throws Exception {
+        moveActivityFullSplitFull(RESIZEABLE_ACTIVITY_NAME);
+    }
+
+    /**
+     * Launches activity in fullscreen stack, moves to docked stack and back to fullscreen stack.
+     * Last operation is done in a way which simulates split-screen divider movement maximizing
+     * docked stack size and then moving task to fullscreen stack - the same way it is done when
+     * user long-presses overview/recents button to exit split-screen.
+     * Asserts that initial and final reported sizes in fullscreen stack are the same.
+     */
+    private void moveActivityFullSplitFull(String activityName) throws Exception {
+        // Launch to fullscreen stack and record size.
+        launchActivityInStack(activityName, FULLSCREEN_WORKSPACE_STACK_ID);
+        final ReportedSizes initialFullscreenSizes = getActivityDisplaySize(activityName,
+                FULLSCREEN_WORKSPACE_STACK_ID);
+        final Rectangle displayRect = getDisplayRect(activityName);
+
+        // Move to docked stack.
+        moveActivityToStack(activityName, DOCKED_STACK_ID);
+        final ReportedSizes dockedSizes = getActivityDisplaySize(activityName, DOCKED_STACK_ID);
+        assertSizesAreSane(initialFullscreenSizes, dockedSizes);
+        // Make sure docked stack is focused. This way when we dismiss it later fullscreen stack
+        // will come up.
+        launchActivityInStack(activityName, DOCKED_STACK_ID);
+        mAmWmState.computeState(mDevice, new String[] { activityName },
+                false /* compareTaskAndStackBounds */);
+
+        // Resize docked stack to fullscreen size. This will trigger activity relaunch with
+        // non-empty override configuration corresponding to fullscreen size.
+        runCommandAndPrintOutput("am stack resize " + DOCKED_STACK_ID + " 0 0 "
+                + displayRect.width + " " + displayRect.height);
+        // Move activity back to fullscreen stack.
+        moveActivityToStack(activityName, FULLSCREEN_WORKSPACE_STACK_ID);
+        final ReportedSizes finalFullscreenSizes = getActivityDisplaySize(activityName,
+                FULLSCREEN_WORKSPACE_STACK_ID);
+
+        // After activity configuration was changed twice it must report same size as original one.
+        assertSizesAreSame(initialFullscreenSizes, finalFullscreenSizes);
+    }
+
+    /**
+     * Tests when activity moved from docked stack to fullscreen and back. Activity will be
+     * relaunched twice and it should have same config as initial one.
+     */
+    public void testSameConfigurationSplitFullSplitRelaunch() throws Exception {
+        moveActivitySplitFullSplit(TEST_ACTIVITY_NAME);
+    }
+
+    /**
+     * Same as {@link #testSameConfigurationSplitFullSplitRelaunch} but without relaunch.
+     */
+    public void testSameConfigurationSplitFullSplitNoRelaunch() throws Exception {
+        moveActivitySplitFullSplit(RESIZEABLE_ACTIVITY_NAME);
+    }
+
+    /**
+     * Launches activity in docked stack, moves to fullscreen stack and back to docked stack.
+     * Asserts that initial and final reported sizes in docked stack are the same.
+     */
+    private void moveActivitySplitFullSplit(String activityName) throws Exception {
+        // Launch to docked stack and record size.
+        launchActivityInStack(activityName, DOCKED_STACK_ID);
+        final ReportedSizes initialDockedSizes = getActivityDisplaySize(activityName,
+                DOCKED_STACK_ID);
+        // Make sure docked stack is focused. This way when we dismiss it later fullscreen stack
+        // will come up.
+        launchActivityInStack(activityName, DOCKED_STACK_ID);
+        mAmWmState.computeState(mDevice, new String[] { activityName },
+                false /* compareTaskAndStackBounds */);
+
+        // Move to fullscreen stack.
+        moveActivityToStack(activityName, FULLSCREEN_WORKSPACE_STACK_ID);
+        final ReportedSizes fullscreenSizes = getActivityDisplaySize(activityName,
+                FULLSCREEN_WORKSPACE_STACK_ID);
+        assertSizesAreSane(fullscreenSizes, initialDockedSizes);
+
+        // Move activity back to docked stack.
+        moveActivityToStack(activityName, DOCKED_STACK_ID);
+        final ReportedSizes finalDockedSizes = getActivityDisplaySize(activityName,
+                DOCKED_STACK_ID);
+
+        // After activity configuration was changed twice it must report same size as original one.
+        assertSizesAreSame(initialDockedSizes, finalDockedSizes);
+    }
+
+    /**
      * Asserts that after rotation, the aspect ratios of display size, metrics, and configuration
      * have flipped.
      */
@@ -126,6 +227,19 @@
         }
     }
 
+    /**
+     * Throws an AssertionError if sizes are different.
+     */
+    private static void assertSizesAreSame(ReportedSizes firstSize, ReportedSizes secondSize)
+            throws Exception {
+        assertEquals(firstSize.widthDp, secondSize.widthDp);
+        assertEquals(firstSize.heightDp, secondSize.heightDp);
+        assertEquals(firstSize.displayWidth, secondSize.displayWidth);
+        assertEquals(firstSize.displayHeight, secondSize.displayHeight);
+        assertEquals(firstSize.metricsWidth, secondSize.metricsWidth);
+        assertEquals(firstSize.metricsHeight, secondSize.metricsHeight);
+    }
+
     private ReportedSizes getActivityDisplaySize(String activityName, int stackId)
             throws Exception {
         mAmWmState.computeState(mDevice, new String[] { activityName },
@@ -135,4 +249,28 @@
         assertNotNull(details);
         return details;
     }
+
+    private Rectangle getDisplayRect(String activityName)
+            throws Exception {
+        final String windowName = getWindowName(activityName);
+
+        mAmWmState.computeState(mDevice, true /* visibleOnly */, new String[] {activityName});
+
+        mAmWmState.assertFocusedWindow("Test window must be the front window.", windowName);
+
+        final List<WindowManagerState.WindowState> tempWindowList = new ArrayList<>();
+        mAmWmState.getWmState().getMatchingWindowState(windowName, tempWindowList);
+
+        assertEquals("Should have exactly one window state for the activity.", 1,
+                tempWindowList.size());
+
+        WindowManagerState.WindowState windowState = tempWindowList.get(0);
+        assertNotNull("Should have a valid window", windowState);
+
+        WindowManagerState.Display display = mAmWmState.getWmState()
+                .getDisplay(windowState.getDisplayId());
+        assertNotNull("Should be on a display", display);
+
+        return display.getDisplayRect();
+    }
 }
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/ActivityManagerDockedStackTests.java b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerDockedStackTests.java
index 8268cb5..034c208 100644
--- a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerDockedStackTests.java
+++ b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerDockedStackTests.java
@@ -23,7 +23,6 @@
     private static final String TEST_ACTIVITY_NAME = "TestActivity";
     private static final String NON_RESIZEABLE_ACTIVITY_NAME = "NonResizeableActivity";
     private static final String DOCKED_ACTIVITY_NAME = "DockedActivity";
-    private static final String LAUNCH_TO_SIDE_ACTIVITY_NAME = "LaunchToSideActivity";
     private static final String NO_RELAUNCH_ACTIVITY_NAME = "NoRelaunchActivity";
     private static final String SINGLE_INSTANCE_ACTIVITY_NAME = "SingleInstanceActivity";
     private static final String SINGLE_TASK_ACTIVITY_NAME = "SingleTaskActivity";
@@ -58,9 +57,9 @@
     }
 
     public void testLaunchToSide() throws Exception {
-        launchActivityInDockStack(LAUNCH_TO_SIDE_ACTIVITY_NAME);
-        launchActivityToSide(LAUNCH_TO_SIDE_ACTIVITY_NAME);
-        mAmWmState.computeState(mDevice, new String[] {LAUNCH_TO_SIDE_ACTIVITY_NAME});
+        launchActivityInDockStack(LAUNCHING_ACTIVITY);
+        launchActivityToSide();
+        mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY, TEST_ACTIVITY_NAME});
 
         mAmWmState.assertContainsStack(
                 "Must contain fullscreen stack.", FULLSCREEN_WORKSPACE_STACK_ID);
@@ -68,12 +67,12 @@
     }
 
     public void testLaunchToSideAndBringToFront() throws Exception {
-        launchActivityInDockStack(LAUNCH_TO_SIDE_ACTIVITY_NAME);
+        launchActivityInDockStack(LAUNCHING_ACTIVITY);
         final String[] waitForFirstVisible = new String[] {TEST_ACTIVITY_NAME};
         final String[] waitForSecondVisible = new String[] {NO_RELAUNCH_ACTIVITY_NAME};
 
         // Launch activity to side.
-        launchActivityToSide(LAUNCH_TO_SIDE_ACTIVITY_NAME);
+        launchActivityToSide();
         mAmWmState.computeState(mDevice, waitForFirstVisible);
         int taskNumberInitial = mAmWmState.getAmState().getStackById(FULLSCREEN_WORKSPACE_STACK_ID)
                 .getTasks().size();
@@ -91,7 +90,7 @@
                 NO_RELAUNCH_ACTIVITY_NAME);
 
         // Launch activity that was first launched to side. It should be brought to front.
-        launchActivityToSide(LAUNCH_TO_SIDE_ACTIVITY_NAME);
+        launchActivityToSide();
         mAmWmState.computeState(mDevice, waitForFirstVisible);
         int taskNumberFinal = mAmWmState.getAmState().getStackById(FULLSCREEN_WORKSPACE_STACK_ID)
                 .getTasks().size();
@@ -102,11 +101,11 @@
     }
 
     public void testLaunchToSideMultiple() throws Exception {
-        launchActivityInDockStack(LAUNCH_TO_SIDE_ACTIVITY_NAME);
+        launchActivityInDockStack(LAUNCHING_ACTIVITY);
         final String[] waitForActivitiesVisible = new String[] {TEST_ACTIVITY_NAME};
 
         // Launch activity to side.
-        launchActivityToSide(LAUNCH_TO_SIDE_ACTIVITY_NAME);
+        launchActivityToSide();
         mAmWmState.computeState(mDevice, waitForActivitiesVisible);
         int taskNumberInitial = mAmWmState.getAmState().getStackById(FULLSCREEN_WORKSPACE_STACK_ID)
                 .getTasks().size();
@@ -115,7 +114,7 @@
                         .getTaskByActivityName(TEST_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID));
 
         // Try to launch to side same activity again.
-        launchActivityToSide(LAUNCH_TO_SIDE_ACTIVITY_NAME);
+        launchActivityToSide();
         mAmWmState.computeState(mDevice, waitForActivitiesVisible);
         int taskNumberFinal = mAmWmState.getAmState().getStackById(FULLSCREEN_WORKSPACE_STACK_ID)
                 .getTasks().size();
@@ -141,11 +140,11 @@
 
     private void launchTargetToSide(String targetActivityName,
                                     boolean taskCountMustIncrement) throws Exception {
-        launchActivityInDockStack(LAUNCH_TO_SIDE_ACTIVITY_NAME);
+        launchActivityInDockStack(LAUNCHING_ACTIVITY);
         final String[] waitForActivitiesVisible = new String[] {targetActivityName};
 
         // Launch activity to side with data.
-        launchActivityToSide(LAUNCH_TO_SIDE_ACTIVITY_NAME, true, false, targetActivityName);
+        launchActivityToSide(true, false, targetActivityName);
         mAmWmState.computeState(mDevice, waitForActivitiesVisible);
         int taskNumberInitial = mAmWmState.getAmState().getStackById(FULLSCREEN_WORKSPACE_STACK_ID)
                 .getTasks().size();
@@ -154,7 +153,7 @@
                         .getTaskByActivityName(targetActivityName, FULLSCREEN_WORKSPACE_STACK_ID));
 
         // Try to launch to side same activity again with different data.
-        launchActivityToSide(LAUNCH_TO_SIDE_ACTIVITY_NAME, true, false, targetActivityName);
+        launchActivityToSide(true, false, targetActivityName);
         mAmWmState.computeState(mDevice, waitForActivitiesVisible);
         int taskNumberSecondLaunch = mAmWmState.getAmState()
                 .getStackById(FULLSCREEN_WORKSPACE_STACK_ID).getTasks().size();
@@ -172,7 +171,7 @@
                         .getTaskByActivityName(targetActivityName, FULLSCREEN_WORKSPACE_STACK_ID));
 
         // Try to launch to side same activity again with no data.
-        launchActivityToSide(LAUNCH_TO_SIDE_ACTIVITY_NAME, false, false, targetActivityName);
+        launchActivityToSide(false, false, targetActivityName);
         mAmWmState.computeState(mDevice, waitForActivitiesVisible);
         int taskNumberFinal = mAmWmState.getAmState().getStackById(FULLSCREEN_WORKSPACE_STACK_ID)
                 .getTasks().size();
@@ -191,11 +190,11 @@
     }
 
     public void testLaunchToSideMultipleWithFlag() throws Exception {
-        launchActivityInDockStack(LAUNCH_TO_SIDE_ACTIVITY_NAME);
+        launchActivityInDockStack(LAUNCHING_ACTIVITY);
         final String[] waitForActivitiesVisible = new String[] {TEST_ACTIVITY_NAME};
 
         // Launch activity to side.
-        launchActivityToSide(LAUNCH_TO_SIDE_ACTIVITY_NAME);
+        launchActivityToSide();
         mAmWmState.computeState(mDevice, waitForActivitiesVisible);
         int taskNumberInitial = mAmWmState.getAmState().getStackById(FULLSCREEN_WORKSPACE_STACK_ID)
                 .getTasks().size();
@@ -204,7 +203,7 @@
                         .getTaskByActivityName(TEST_ACTIVITY_NAME, FULLSCREEN_WORKSPACE_STACK_ID));
 
         // Try to launch to side same activity again, but with Intent#FLAG_ACTIVITY_MULTIPLE_TASK.
-        launchActivityToSide(LAUNCH_TO_SIDE_ACTIVITY_NAME, false, true);
+        launchActivityToSide(false, true);
         mAmWmState.computeState(mDevice, waitForActivitiesVisible);
         int taskNumberFinal = mAmWmState.getAmState().getStackById(FULLSCREEN_WORKSPACE_STACK_ID)
                 .getTasks().size();
@@ -218,9 +217,9 @@
     }
 
     public void testRotationWhenDocked() throws Exception {
-        launchActivityInDockStack(LAUNCH_TO_SIDE_ACTIVITY_NAME);
-        launchActivityToSide(LAUNCH_TO_SIDE_ACTIVITY_NAME);
-        final String[] waitForActivitiesVisible = new String[] {LAUNCH_TO_SIDE_ACTIVITY_NAME};
+        launchActivityInDockStack(LAUNCHING_ACTIVITY);
+        launchActivityToSide();
+        final String[] waitForActivitiesVisible = new String[] {LAUNCHING_ACTIVITY};
         mAmWmState.computeState(mDevice, waitForActivitiesVisible);
         mAmWmState.assertContainsStack(
                 "Must contain fullscreen stack.", FULLSCREEN_WORKSPACE_STACK_ID);
@@ -251,11 +250,10 @@
     }
 
     public void testRotationWhenDockedWhileLocked() throws Exception {
-        launchActivityInDockStack(LAUNCH_TO_SIDE_ACTIVITY_NAME);
-        launchActivityToSide(LAUNCH_TO_SIDE_ACTIVITY_NAME);
-        final String[] waitForActivitiesVisible = new String[] {LAUNCH_TO_SIDE_ACTIVITY_NAME};
+        launchActivityInDockStack(LAUNCHING_ACTIVITY);
+        launchActivityToSide();
+        final String[] waitForActivitiesVisible = new String[] {LAUNCHING_ACTIVITY};
         mAmWmState.computeState(mDevice, waitForActivitiesVisible);
-        mAmWmState.assertSanity();
         mAmWmState.assertContainsStack(
                 "Must contain fullscreen stack.", FULLSCREEN_WORKSPACE_STACK_ID);
         mAmWmState.assertContainsStack("Must contain docked stack.", DOCKED_STACK_ID);
@@ -345,29 +343,17 @@
         return newBounds;
     }
 
-    private void launchActivityToSide(String activityName) throws Exception {
-        launchActivityToSide(activityName, false, false);
+    private void launchActivityToSide() throws Exception {
+        launchActivityToSide(false, false);
     }
 
-    private void launchActivityToSide(String activityName, boolean randomData,
-                                      boolean multipleTaskFlag) throws Exception {
-        launchActivityToSide(activityName, randomData, multipleTaskFlag, null);
-    }
-
-    private void launchActivityToSide(String activityName, boolean randomData,
-                                      boolean multipleTaskFlag, String targetActivityName)
+    private void launchActivityToSide(boolean randomData, boolean multipleTaskFlag)
             throws Exception {
-        StringBuilder commandBuilder = new StringBuilder(getAmStartCmd(activityName));
-        commandBuilder.append(" -f 0x20000000 --ez launch_to_the_side true");
-        if (randomData) {
-            commandBuilder.append(" --ez random_data true");
-        }
-        if (multipleTaskFlag) {
-            commandBuilder.append(" --ez multiple_task true");
-        }
-        if (targetActivityName != null) {
-            commandBuilder.append(" --es target_activity ").append(targetActivityName);
-        }
-        executeShellCommand(commandBuilder.toString());
+        launchActivityToSide(randomData, multipleTaskFlag, null);
+    }
+
+    private void launchActivityToSide(boolean randomData, boolean multipleTaskFlag,
+            String targetActivity) throws Exception {
+        launchActivity(true /* toSide */, randomData, multipleTaskFlag, targetActivity);
     }
 }
diff --git a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerTestBase.java b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerTestBase.java
index 3a21fda..f4d84c7 100644
--- a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerTestBase.java
+++ b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerTestBase.java
@@ -70,10 +70,14 @@
     protected static final String AM_MOVE_TOP_ACTIVITY_TO_PINNED_STACK_COMMAND =
             "am stack move-top-activity-to-pinned-stack 1 0 0 500 500";
 
+    protected static final String LAUNCHING_ACTIVITY = "LaunchingActivity";
+
     private static final String AM_RESIZE_DOCKED_STACK = "am stack resize-docked-stack ";
 
     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 +103,7 @@
 
     private int mInitialAccelerometerRotation;
     private int mUserRotation;
+    private float mFontScale;
 
     @Override
     protected void setUp() throws Exception {
@@ -114,6 +119,7 @@
         // Store rotation settings.
         mInitialAccelerometerRotation = getAccelerometerRotation();
         mUserRotation = getUserRotation();
+        mFontScale = getFontScale();
     }
 
     @Override
@@ -125,6 +131,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);
@@ -144,6 +151,36 @@
         mDevice.executeShellCommand(command, outputReceiver);
     }
 
+    /**
+     * Launch specific target activity. It uses existing instance of {@link #LAUNCHING_ACTIVITY}, so
+     * that one should be started first.
+     * @param toSide Launch to side in split-screen.
+     * @param randomData Make intent URI random by generating random data.
+     * @param multipleTask Allow multiple task launch.
+     * @param targetActivityName Target activity to be launched. Only class name should be provided,
+     *                           package name of {@link #LAUNCHING_ACTIVITY} will be added
+     *                           automatically.
+     * @throws Exception
+     */
+    protected void launchActivity(boolean toSide, boolean randomData, boolean multipleTask,
+            String targetActivityName) throws Exception {
+        StringBuilder commandBuilder = new StringBuilder(getAmStartCmd(LAUNCHING_ACTIVITY));
+        commandBuilder.append(" -f 0x20000000");
+        if (toSide) {
+            commandBuilder.append(" --ez launch_to_the_side true");
+        }
+        if (randomData) {
+            commandBuilder.append(" --ez random_data true");
+        }
+        if (multipleTask) {
+            commandBuilder.append(" --ez multiple_task true");
+        }
+        if (targetActivityName != null) {
+            commandBuilder.append(" --es target_activity ").append(targetActivityName);
+        }
+        executeShellCommand(commandBuilder.toString());
+    }
+
     protected void launchActivityInStack(String activityName, int stackId) throws Exception {
         executeShellCommand(getAmStartCmd(activityName) + " --stack " + stackId);
     }
@@ -179,6 +216,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 +360,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 +422,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 +476,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 +500,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/hostsidetests/theme/Android.mk b/hostsidetests/theme/Android.mk
index d58b85e..00ca6ba 100644
--- a/hostsidetests/theme/Android.mk
+++ b/hostsidetests/theme/Android.mk
@@ -18,7 +18,7 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_JAVA_RESOURCE_DIRS := assets/$(PLATFORM_SDK_VERSION)/
+LOCAL_JAVA_RESOURCE_DIRS := assets/
 
 LOCAL_MODULE_TAGS := optional
 
diff --git a/hostsidetests/theme/assets/17/400dpi.zip b/hostsidetests/theme/assets/17/400dpi.zip
deleted file mode 100644
index efcfa0b..0000000
--- a/hostsidetests/theme/assets/17/400dpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/17/hdpi.zip b/hostsidetests/theme/assets/17/hdpi.zip
deleted file mode 100644
index b10eedf..0000000
--- a/hostsidetests/theme/assets/17/hdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/17/ldpi.zip b/hostsidetests/theme/assets/17/ldpi.zip
deleted file mode 100644
index ef9c883..0000000
--- a/hostsidetests/theme/assets/17/ldpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/17/mdpi.zip b/hostsidetests/theme/assets/17/mdpi.zip
deleted file mode 100644
index 5e8a04d..0000000
--- a/hostsidetests/theme/assets/17/mdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/17/tvdpi.zip b/hostsidetests/theme/assets/17/tvdpi.zip
deleted file mode 100644
index ad71d4e..0000000
--- a/hostsidetests/theme/assets/17/tvdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/17/xhdpi.zip b/hostsidetests/theme/assets/17/xhdpi.zip
deleted file mode 100644
index d48620d..0000000
--- a/hostsidetests/theme/assets/17/xhdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/18/400dpi.zip b/hostsidetests/theme/assets/18/400dpi.zip
deleted file mode 100644
index 21d2ea8..0000000
--- a/hostsidetests/theme/assets/18/400dpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/18/hdpi.zip b/hostsidetests/theme/assets/18/hdpi.zip
deleted file mode 100644
index 3f7fb6d..0000000
--- a/hostsidetests/theme/assets/18/hdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/18/ldpi.zip b/hostsidetests/theme/assets/18/ldpi.zip
deleted file mode 100644
index 957a6e3..0000000
--- a/hostsidetests/theme/assets/18/ldpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/18/mdpi.zip b/hostsidetests/theme/assets/18/mdpi.zip
deleted file mode 100644
index 833a8d5..0000000
--- a/hostsidetests/theme/assets/18/mdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/18/tvdpi.zip b/hostsidetests/theme/assets/18/tvdpi.zip
deleted file mode 100644
index 017f059..0000000
--- a/hostsidetests/theme/assets/18/tvdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/18/xhdpi.zip b/hostsidetests/theme/assets/18/xhdpi.zip
deleted file mode 100644
index 4bcdcae..0000000
--- a/hostsidetests/theme/assets/18/xhdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/19/400dpi.zip b/hostsidetests/theme/assets/19/400dpi.zip
deleted file mode 100644
index 2f9edd2..0000000
--- a/hostsidetests/theme/assets/19/400dpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/19/hdpi.zip b/hostsidetests/theme/assets/19/hdpi.zip
deleted file mode 100644
index 45f07f4..0000000
--- a/hostsidetests/theme/assets/19/hdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/19/ldpi.zip b/hostsidetests/theme/assets/19/ldpi.zip
deleted file mode 100644
index 89d6544..0000000
--- a/hostsidetests/theme/assets/19/ldpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/19/mdpi.zip b/hostsidetests/theme/assets/19/mdpi.zip
deleted file mode 100644
index 6559c4a..0000000
--- a/hostsidetests/theme/assets/19/mdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/19/tvdpi.zip b/hostsidetests/theme/assets/19/tvdpi.zip
deleted file mode 100644
index 6496a1f7..0000000
--- a/hostsidetests/theme/assets/19/tvdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/19/xhdpi.zip b/hostsidetests/theme/assets/19/xhdpi.zip
deleted file mode 100644
index 515dcf2..0000000
--- a/hostsidetests/theme/assets/19/xhdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/19/xxhdpi.zip b/hostsidetests/theme/assets/19/xxhdpi.zip
deleted file mode 100644
index c9c7a59..0000000
--- a/hostsidetests/theme/assets/19/xxhdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/21/400dpi.zip b/hostsidetests/theme/assets/21/400dpi.zip
deleted file mode 100644
index 6d62e5b..0000000
--- a/hostsidetests/theme/assets/21/400dpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/21/560dpi.zip b/hostsidetests/theme/assets/21/560dpi.zip
deleted file mode 100644
index eff363c..0000000
--- a/hostsidetests/theme/assets/21/560dpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/21/hdpi.zip b/hostsidetests/theme/assets/21/hdpi.zip
deleted file mode 100644
index 0fa67b7..0000000
--- a/hostsidetests/theme/assets/21/hdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/21/ldpi.zip b/hostsidetests/theme/assets/21/ldpi.zip
deleted file mode 100644
index e33f2f0..0000000
--- a/hostsidetests/theme/assets/21/ldpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/21/mdpi.zip b/hostsidetests/theme/assets/21/mdpi.zip
deleted file mode 100644
index f74739e..0000000
--- a/hostsidetests/theme/assets/21/mdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/21/tvdpi.zip b/hostsidetests/theme/assets/21/tvdpi.zip
deleted file mode 100644
index fbe1781..0000000
--- a/hostsidetests/theme/assets/21/tvdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/21/xhdpi.zip b/hostsidetests/theme/assets/21/xhdpi.zip
deleted file mode 100644
index de6e2e1..0000000
--- a/hostsidetests/theme/assets/21/xhdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/21/xxhdpi.zip b/hostsidetests/theme/assets/21/xxhdpi.zip
deleted file mode 100644
index 9f0d778..0000000
--- a/hostsidetests/theme/assets/21/xxhdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/22/400dpi.zip b/hostsidetests/theme/assets/22/400dpi.zip
deleted file mode 100644
index 6d62e5b..0000000
--- a/hostsidetests/theme/assets/22/400dpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/22/560dpi.zip b/hostsidetests/theme/assets/22/560dpi.zip
deleted file mode 100644
index eff363c..0000000
--- a/hostsidetests/theme/assets/22/560dpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/22/hdpi.zip b/hostsidetests/theme/assets/22/hdpi.zip
deleted file mode 100644
index 0fa67b7..0000000
--- a/hostsidetests/theme/assets/22/hdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/22/ldpi.zip b/hostsidetests/theme/assets/22/ldpi.zip
deleted file mode 100644
index e33f2f0..0000000
--- a/hostsidetests/theme/assets/22/ldpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/22/mdpi.zip b/hostsidetests/theme/assets/22/mdpi.zip
deleted file mode 100644
index f74739e..0000000
--- a/hostsidetests/theme/assets/22/mdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/22/tvdpi.zip b/hostsidetests/theme/assets/22/tvdpi.zip
deleted file mode 100644
index fbe1781..0000000
--- a/hostsidetests/theme/assets/22/tvdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/22/xhdpi.zip b/hostsidetests/theme/assets/22/xhdpi.zip
deleted file mode 100644
index de6e2e1..0000000
--- a/hostsidetests/theme/assets/22/xhdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/22/xxhdpi.zip b/hostsidetests/theme/assets/22/xxhdpi.zip
deleted file mode 100644
index 9f0d778..0000000
--- a/hostsidetests/theme/assets/22/xxhdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/23/400dpi.zip b/hostsidetests/theme/assets/23/400dpi.zip
deleted file mode 100644
index be0891f..0000000
--- a/hostsidetests/theme/assets/23/400dpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/23/420dpi.zip b/hostsidetests/theme/assets/23/420dpi.zip
deleted file mode 100644
index ecf1d54..0000000
--- a/hostsidetests/theme/assets/23/420dpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/23/560dpi.zip b/hostsidetests/theme/assets/23/560dpi.zip
deleted file mode 100644
index 231b4f5..0000000
--- a/hostsidetests/theme/assets/23/560dpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/23/hdpi.zip b/hostsidetests/theme/assets/23/hdpi.zip
deleted file mode 100644
index 80c12a7..0000000
--- a/hostsidetests/theme/assets/23/hdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/23/ldpi.zip b/hostsidetests/theme/assets/23/ldpi.zip
deleted file mode 100644
index 937914a..0000000
--- a/hostsidetests/theme/assets/23/ldpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/23/mdpi.zip b/hostsidetests/theme/assets/23/mdpi.zip
deleted file mode 100644
index f842676..0000000
--- a/hostsidetests/theme/assets/23/mdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/23/tvdpi.zip b/hostsidetests/theme/assets/23/tvdpi.zip
deleted file mode 100644
index 77386e5..0000000
--- a/hostsidetests/theme/assets/23/tvdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/23/xhdpi.zip b/hostsidetests/theme/assets/23/xhdpi.zip
deleted file mode 100644
index a8310d5..0000000
--- a/hostsidetests/theme/assets/23/xhdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/23/xxhdpi.zip b/hostsidetests/theme/assets/23/xxhdpi.zip
deleted file mode 100644
index f88711f..0000000
--- a/hostsidetests/theme/assets/23/xxhdpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/24/420dpi.zip b/hostsidetests/theme/assets/420dpi.zip
similarity index 100%
rename from hostsidetests/theme/assets/24/420dpi.zip
rename to hostsidetests/theme/assets/420dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/24/560dpi.zip b/hostsidetests/theme/assets/560dpi.zip
similarity index 100%
rename from hostsidetests/theme/assets/24/560dpi.zip
rename to hostsidetests/theme/assets/560dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/24/xhdpi.zip b/hostsidetests/theme/assets/xhdpi.zip
similarity index 100%
rename from hostsidetests/theme/assets/24/xhdpi.zip
rename to hostsidetests/theme/assets/xhdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/24/xxhdpi.zip b/hostsidetests/theme/assets/xxhdpi.zip
similarity index 100%
rename from hostsidetests/theme/assets/24/xxhdpi.zip
rename to hostsidetests/theme/assets/xxhdpi.zip
Binary files differ
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/ActivityManagerTest.java b/tests/app/src/android/app/cts/ActivityManagerTest.java
index e46bfbd..9049171 100644
--- a/tests/app/src/android/app/cts/ActivityManagerTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerTest.java
@@ -510,7 +510,7 @@
 
         // The application finished tracker.
         ActivityReceiverFilter appEndReceiver = new ActivityReceiverFilter(
-                ACTIVITY_LAUNCHED_ACTION);
+                ACTIVITY_CHAIN_EXIT_ACTION);
 
         // The filter for the time event.
         ActivityReceiverFilter timeReceiver = new ActivityReceiverFilter(ACTIVITY_TIME_TRACK_INFO);
diff --git a/tests/app/src/android/app/cts/DialogTest.java b/tests/app/src/android/app/cts/DialogTest.java
index 8ae4c66..fa22ce4 100755
--- a/tests/app/src/android/app/cts/DialogTest.java
+++ b/tests/app/src/android/app/cts/DialogTest.java
@@ -370,6 +370,8 @@
     public void testTouchEvent() {
         startDialogActivity(DialogStubActivity.TEST_ONSTART_AND_ONSTOP);
         final TestDialog d = (TestDialog) mActivity.getDialog();
+        final Rect containingRect = new Rect();
+        mActivity.getWindow().getDecorView().getWindowVisibleDisplayFrame(containingRect);
 
         assertNull(d.onTouchEvent);
         assertNull(d.touchEvent);
@@ -381,7 +383,7 @@
 
         long now = SystemClock.uptimeMillis();
         MotionEvent touchMotionEvent = MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN,
-                1, getStatusBarHeight(), 0);
+                containingRect.left + 1, containingRect.top + 1, 0);
         mInstrumentation.sendPointerSync(touchMotionEvent);
 
         new PollingCheck(TEST_TIMEOUT) {
@@ -404,7 +406,7 @@
             d.setCanceledOnTouchOutside(true);
 
             touchMotionEvent = MotionEvent.obtain(now, now + 1, MotionEvent.ACTION_DOWN,
-                    1, getStatusBarHeight(), 0);
+                    containingRect.left + 1, containingRect.top + 1, 0);
             mInstrumentation.sendPointerSync(touchMotionEvent);
 
             new PollingCheck(TEST_TIMEOUT) {
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/RecordingTest.java b/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java
index 1a09a43..d101036 100644
--- a/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java
@@ -50,6 +50,7 @@
 import java.io.File;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.HashMap;
 
@@ -65,6 +66,9 @@
     private static final int RECORDING_DURATION_MS = 3000;
     private static final float DURATION_MARGIN = 0.2f;
     private static final double FRAME_DURATION_ERROR_TOLERANCE_MS = 3.0;
+    private static final float FRAMEDURATION_MARGIN = 0.2f;
+    private static final float VID_SNPSHT_FRMDRP_RATE_TOLERANCE = 10.0f;
+    private static final float FRMDRP_RATE_TOLERANCE = 5.0f;
     private static final int BIT_RATE_1080P = 16000000;
     private static final int BIT_RATE_MIN = 64000;
     private static final int BIT_RATE_MAX = 40000000;
@@ -417,11 +421,11 @@
                     // Stop recording and preview
                     stopRecording(/*useMediaRecorder*/true);
                     // Convert number of frames camera produced into the duration in unit of ms.
-                    int durationMs = (int) (resultListener.getTotalNumFrames() * 1000.0f /
-                                    videoFramerate);
+                    float frameDurationMs = 1000.0f / videoFramerate;
+                    float durationMs = resultListener.getTotalNumFrames() * frameDurationMs;
 
                     // Validation.
-                    validateRecording(size, durationMs);
+                    validateRecording(size, durationMs, frameDurationMs, FRMDRP_RATE_TOLERANCE);
                 }
 
             } finally {
@@ -484,11 +488,11 @@
                         // Stop recording and preview
                         stopRecording(/*useMediaRecorder*/true);
                         // Convert number of frames camera produced into the duration in unit of ms.
-                        int durationMs = (int) (resultListener.getTotalNumFrames() * 1000.0f /
-                                        VIDEO_FRAME_RATE);
+                        float frameDurationMs = 1000.0f / VIDEO_FRAME_RATE;
+                        float durationMs = resultListener.getTotalNumFrames() * frameDurationMs;
 
                         // Validation.
-                        validateRecording(size, durationMs);
+                        validateRecording(size, durationMs, frameDurationMs, FRMDRP_RATE_TOLERANCE);
                     }
                 }
 
@@ -682,8 +686,8 @@
             // Stop recording and preview
             stopRecording(/* useMediaRecorder */true);
             // Convert number of frames camera produced into the duration in unit of ms.
-            int durationMs = (int) (resultListener.getTotalNumFrames() * 1000.0f /
-                            profile.videoFrameRate);
+            float frameDurationMs = 1000.0f / profile.videoFrameRate;
+            float durationMs = resultListener.getTotalNumFrames() * frameDurationMs;
 
             if (VERBOSE) {
                 Log.v(TAG, "video frame rate: " + profile.videoFrameRate +
@@ -691,7 +695,7 @@
             }
 
             // Validation.
-            validateRecording(videoSz, durationMs);
+            validateRecording(videoSz, durationMs, frameDurationMs, FRMDRP_RATE_TOLERANCE);
         }
         if (maxVideoFrameRate != -1) {
             // At least one CamcorderProfile is present, check FPS
@@ -737,11 +741,11 @@
             // Stop recording and preview
             stopRecording(/* useMediaRecorder */true);
             // Convert number of frames camera produced into the duration in unit of ms.
-            int durationMs = (int) (resultListener.getTotalNumFrames() * 1000.0f /
-                            VIDEO_FRAME_RATE);
+            float frameDurationMs = 1000.0f / VIDEO_FRAME_RATE;
+            float durationMs = resultListener.getTotalNumFrames() * frameDurationMs;
 
             // Validation.
-            validateRecording(sz, durationMs);
+            validateRecording(sz, durationMs, frameDurationMs, FRMDRP_RATE_TOLERANCE);
         }
     }
 
@@ -992,17 +996,19 @@
                 SystemClock.sleep(RECORDING_DURATION_MS / 2);
 
                 // Stop recording and preview
-                int durationMs = stopRecording(/* useMediaRecorder */true);
+                float durationMs = (float) stopRecording(/* useMediaRecorder */true);
                 // For non-burst test, use number of frames to also double check video frame rate.
                 // Burst video snapshot is allowed to cause frame rate drop, so do not use number
                 // of frames to estimate duration
                 if (!burstTest) {
-                    durationMs = (int) (resultListener.getTotalNumFrames() * 1000.0f /
-                        profile.videoFrameRate);
+                    durationMs = resultListener.getTotalNumFrames() * 1000.0f /
+                        profile.videoFrameRate;
                 }
 
+                float frameDurationMs = 1000.0f / profile.videoFrameRate;
                 // Validation recorded video
-                validateRecording(videoSz, durationMs);
+                validateRecording(videoSz, durationMs,
+                        frameDurationMs, VID_SNPSHT_FRMDRP_RATE_TOLERANCE);
 
                 if (burstTest) {
                     for (int i = 0; i < BURST_VIDEO_SNAPSHOT_NUM; i++) {
@@ -1235,16 +1241,19 @@
         }
     }
 
-    private void validateRecording(Size sz, int expectedDurationMs) throws Exception {
+    private void validateRecording(
+            Size sz, float expectedDurationMs, float expectedFrameDurationMs,
+            float frameDropTolerance) throws Exception {
         File outFile = new File(mOutMediaFileName);
         assertTrue("No video is recorded", outFile.exists());
-
+        float maxFrameDuration = expectedFrameDurationMs * (1.0f + FRAMEDURATION_MARGIN);
         MediaExtractor extractor = new MediaExtractor();
         try {
             extractor.setDataSource(mOutMediaFileName);
             long durationUs = 0;
             int width = -1, height = -1;
             int numTracks = extractor.getTrackCount();
+            int selectedTrack = -1;
             final String VIDEO_MIME_TYPE = "video";
             for (int i = 0; i < numTracks; i++) {
                 MediaFormat format = extractor.getTrackFormat(i);
@@ -1254,26 +1263,65 @@
                     durationUs = format.getLong(MediaFormat.KEY_DURATION);
                     width = format.getInteger(MediaFormat.KEY_WIDTH);
                     height = format.getInteger(MediaFormat.KEY_HEIGHT);
+                    selectedTrack = i;
+                    extractor.selectTrack(i);
                     break;
                 }
             }
+            if (selectedTrack < 0) {
+                throw new AssertionFailedError(
+                        "Cannot find video track!");
+            }
+
             Size videoSz = new Size(width, height);
             assertTrue("Video size doesn't match, expected " + sz.toString() +
                     " got " + videoSz.toString(), videoSz.equals(sz));
-            int duration = (int) (durationUs / 1000);
+            float duration = (float) (durationUs / 1000);
             if (VERBOSE) {
-                Log.v(TAG, String.format("Video duration: recorded %dms, expected %dms",
+                Log.v(TAG, String.format("Video duration: recorded %fms, expected %fms",
                                          duration, expectedDurationMs));
             }
 
             // TODO: Don't skip this for video snapshot
             if (!mStaticInfo.isHardwareLevelLegacy()) {
                 assertTrue(String.format(
-                        "Camera %s: Video duration doesn't match: recorded %dms, expected %dms.",
+                        "Camera %s: Video duration doesn't match: recorded %fms, expected %fms.",
                         mCamera.getId(), duration, expectedDurationMs),
                         Math.abs(duration - expectedDurationMs) <
                         DURATION_MARGIN * expectedDurationMs);
             }
+
+            // Check for framedrop
+            long lastSampleUs = 0;
+            int frameDropCount = 0;
+            int expectedFrameCount = (int) (expectedDurationMs / expectedFrameDurationMs);
+            ArrayList<Long> timestamps = new ArrayList<Long>(expectedFrameCount);
+            while (true) {
+                timestamps.add(extractor.getSampleTime());
+                if (!extractor.advance()) {
+                    break;
+                }
+            }
+            Collections.sort(timestamps);
+            long prevSampleUs = timestamps.get(0);
+            for (int i = 1; i < timestamps.size(); i++) {
+                long currentSampleUs = timestamps.get(i);
+                float frameDurationMs = (float) (currentSampleUs - prevSampleUs) / 1000;
+                if (frameDurationMs > maxFrameDuration) {
+                    Log.w(TAG, String.format(
+                        "Frame drop at %d: expectation %f, observed %f",
+                        i, expectedFrameDurationMs, frameDurationMs));
+                    frameDropCount++;
+                }
+                prevSampleUs = currentSampleUs;
+            }
+            float frameDropRate = 100.f * frameDropCount / expectedFrameCount;
+            Log.i(TAG, String.format("Frame drop rate %d/%d (%f%%)",
+                frameDropCount, expectedFrameCount, frameDropRate));
+            assertTrue(String.format(
+                    "Camera %s: Video frame drop rate too high: %f%%, tolerance %f%%.",
+                    mCamera.getId(), frameDropRate, frameDropTolerance),
+                    frameDropRate < frameDropTolerance);
         } finally {
             extractor.release();
             if (!DEBUG_DUMP) {
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/expectations/knownfailures.txt b/tests/expectations/knownfailures.txt
index 5ea4322..60a461c 100644
--- a/tests/expectations/knownfailures.txt
+++ b/tests/expectations/knownfailures.txt
@@ -151,13 +151,6 @@
   bug: 17530117
 },
 {
-  description: "Current implementation of uninstallAllUserCaCerts does not throw expected security exception, wait for fix from framework",
-  names: [
-    "android.admin.cts.DevicePolicyManagerTest#testUninstallAllUserCaCerts_failIfNotProfileOwner"
-  ],
-  bug: 17508787
-},
-{
   description: "Test is not yet properly implemented",
   names: [
     "android.voicesettings.cts.ZenModeTest#testAll"
diff --git a/tests/leanbackjank/src/android/leanbackjank/cts/CtsJankTestBase.java b/tests/leanbackjank/src/android/leanbackjank/cts/CtsJankTestBase.java
index 3db269c..3cff9f8 100644
--- a/tests/leanbackjank/src/android/leanbackjank/cts/CtsJankTestBase.java
+++ b/tests/leanbackjank/src/android/leanbackjank/cts/CtsJankTestBase.java
@@ -46,7 +46,12 @@
         if (!metrics.containsKey(key)) {
             return;
         }
-        mLog.addValue(source, key, metrics.getDouble(key), resultType, resultUnit);
+        mLog.addValue(source, formatKeyForTestMetrics(key), metrics.getDouble(key), resultType,
+                resultUnit);
+    }
+
+    private String formatKeyForTestMetrics(String key) {
+        return key.toLowerCase().replace('-', '_');
     }
 
     @Override
diff --git a/tests/tests/assist/src/android/assist/cts/AssistTestBase.java b/tests/tests/assist/src/android/assist/cts/AssistTestBase.java
index 29b35c5..05d655a 100644
--- a/tests/tests/assist/src/android/assist/cts/AssistTestBase.java
+++ b/tests/tests/assist/src/android/assist/cts/AssistTestBase.java
@@ -270,21 +270,22 @@
      */
     public void verifyHierarchy(AssistStructure structure, boolean isSecureWindow) {
         Log.i(TAG, "verifyHierarchy");
-        Window mWindow = mTestActivity.getWindow();
 
         int numWindows = structure.getWindowNodeCount();
         // TODO: multiple windows?
         assertEquals("Number of windows don't match", 1, numWindows);
+        int[] appLocationOnScreen = new int[2];
+        mView.getLocationOnScreen(appLocationOnScreen);
 
         for (int i = 0; i < numWindows; i++) {
             AssistStructure.WindowNode windowNode = structure.getWindowNodeAt(i);
             Log.i(TAG, "Title: " + windowNode.getTitle());
-            // verify top level window bounds are as big as the screen and pinned to 0,0
+            // Verify top level window bounds are as big as the app and pinned to its top-left
+            // corner.
             assertEquals("Window left position wrong: was " + windowNode.getLeft(),
-                    windowNode.getLeft(), 0);
+                    windowNode.getLeft(), appLocationOnScreen[0]);
             assertEquals("Window top position wrong: was " + windowNode.getTop(),
-                    windowNode.getTop(), 0);
-
+                    windowNode.getTop(), appLocationOnScreen[1]);
             traverseViewAndStructure(
                     mView,
                     windowNode.getRootViewNode(),
diff --git a/tests/tests/content/res/values-v26/strings.xml b/tests/tests/content/res/values-v26/strings.xml
new file mode 100644
index 0000000..d670e02
--- /dev/null
+++ b/tests/tests/content/res/values-v26/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+   <string name="version_cur">v26cur</string>
+</resources>
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/content/src/android/content/res/cts/PrivateAttributeTest.java b/tests/tests/content/src/android/content/res/cts/PrivateAttributeTest.java
index f80b628..cf2f0bc 100644
--- a/tests/tests/content/src/android/content/res/cts/PrivateAttributeTest.java
+++ b/tests/tests/content/src/android/content/res/cts/PrivateAttributeTest.java
@@ -26,7 +26,7 @@
  */
 public class PrivateAttributeTest extends AndroidTestCase {
 
-    private static final int sLastPublicAttr = 0x01010527;
+    private static final int sLastPublicAttr = 0x01010530;
 
     public void testNoAttributesAfterLastPublicAttribute() throws Exception {
         final Resources res = getContext().getResources();
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/cts/PaintTest.java b/tests/tests/graphics/src/android/graphics/cts/PaintTest.java
index d50897fe..bc04657 100644
--- a/tests/tests/graphics/src/android/graphics/cts/PaintTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/PaintTest.java
@@ -1170,12 +1170,20 @@
             assertTrue(p.hasGlyph("\u182D\u180B"));
         }
 
-        // TODO: when we support variation selectors, add positive tests
+        // Emoji with variation selector support for both text and emoji presentation
+        assertTrue(p.hasGlyph("\u231A\uFE0E"));  // WATCH + VS15
+        assertTrue(p.hasGlyph("\u231A\uFE0F"));  // WATCH + VS16
 
         // Unicode 7.0, 8.0, and 9.0 emoji should be supported.
         assertTrue(p.hasGlyph("\uD83D\uDD75"));  // SLEUTH OR SPY is introduced in Unicode 7.0
         assertTrue(p.hasGlyph("\uD83C\uDF2E"));  // TACO is introduced in Unicode 8.0
         assertTrue(p.hasGlyph("\uD83E\uDD33"));  // SELFIE is introduced in Unicode 9.0
+
+        // We don't require gender-neutral emoji, but if present, results must be consistent
+        // whether VS is present or not.
+        assertTrue(p.hasGlyph("\uD83D\uDC69\u200D\u2695") ==  // WOMAN, ZWJ, STAFF OF AESCULAPIUS
+                p.hasGlyph("\uD83D\uDC69\u200D\u2695\uFE0F"));  // above + VS16
+
     }
 
     public void testGetRunAdvance() {
diff --git a/tests/tests/graphics/src/android/graphics/cts/PathTest.java b/tests/tests/graphics/src/android/graphics/cts/PathTest.java
index 6e93400..5958a84 100644
--- a/tests/tests/graphics/src/android/graphics/cts/PathTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/PathTest.java
@@ -404,6 +404,24 @@
         assertFalse(path.isEmpty());
     }
 
+    public void testOffsetTextPath() {
+        Paint paint = new Paint();
+        Path path = new Path();
+        paint.setTextSize(20);
+        String text = "abc";
+        paint.getTextPath(text, 0, text.length() - 1, 0, 0, path);
+        RectF expectedRect = new RectF();
+        path.computeBounds(expectedRect, false);
+        assertFalse(expectedRect.isEmpty());
+        int offset = 10;
+        expectedRect.offset(offset, offset);
+
+        path.offset(offset, offset);
+        RectF offsettedRect = new RectF();
+        path.computeBounds(offsettedRect, false);
+        assertEquals(expectedRect, offsettedRect);
+    }
+
     private static void assertPathsAreEquivalent(Path actual, Path expected) {
         Bitmap actualBitmap = drawAndGetBitmap(actual);
         Bitmap expectedBitmap = drawAndGetBitmap(expected);
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/AndroidTest.xml b/tests/tests/media/AndroidTest.xml
index c08c993..07f5e3f 100644
--- a/tests/tests/media/AndroidTest.xml
+++ b/tests/tests/media/AndroidTest.xml
@@ -18,6 +18,11 @@
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsMediaTestCases.apk" />
     </target_preparer>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusher">
+        <option name="target" value="device" />
+        <option name="config-filename" value="CtsMediaTestCases" />
+        <option name="version" value="7.0"/>
+    </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.media.cts" />
         <!-- test-timeout unit is ms, value = 30 min -->
diff --git a/tests/tests/media/DynamicConfig.xml b/tests/tests/media/DynamicConfig.xml
index 1299440..b39e3fb 100644
--- a/tests/tests/media/DynamicConfig.xml
+++ b/tests/tests/media/DynamicConfig.xml
@@ -14,40 +14,40 @@
 -->
 
 <dynamicConfig>
-    <entry key="DecoderTest-VIDEO_URL">
+    <entry key="decoder_test_audio_url">
         <value>http://redirector.gvt1.com/videoplayback?id=c80658495af60617&amp;itag=18&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=46A04ED550CA83B79B60060BA80C79FDA5853D26.49582D382B4A9AFAA163DED38D2AE531D85603C0&amp;key=ik0&amp;user=android-device-test</value>
     </entry>
-    <entry key="StreamingMediaPlayerTest-testHTTP_H264Base_AAC_Video1">
-        <value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=18&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=667AEEF54639926662CE62361400B8F8C1753B3F.15F46C382C68A9F121BA17BF1F56BEDEB4B06091&amp;key=ik0&amp;user=android-device-test</value>
+    <entry key="decoder_test_video_url">
+        <value>http://redirector.gvt1.com/videoplayback?id=c80658495af60617&amp;itag=18&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=46A04ED550CA83B79B60060BA80C79FDA5853D26.49582D382B4A9AFAA163DED38D2AE531D85603C0&amp;key=ik0&amp;user=android-device-test</value>
     </entry>
-    <entry key="StreamingMediaPlayerTest-testHTTP_H264Base_AAC_Video2">
-        <value>http://www.youtube.com/api/manifest/hls_variant/id/0168724d02bd9945/itag/5/source/youtube/playlist_type/DVR/ip/0.0.0.0/ipbits/0/expire/19000000000/sparams/ip,ipbits,expire,id,itag,source,playlist_type/signature/773AB8ACC68A96E5AA481996AD6A1BBCB70DCB87.95733B544ACC5F01A1223A837D2CF04DF85A3360/key/ik0/file/m3u8</value>
-    </entry>
-    <entry key="MediaCodecCapabilitiesTest-testAvcHigh31">
-        <value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=22&amp;source=youtube&amp;user=android-device-test&amp;sparams=ip,ipbits,expire,id,itag,source,user&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;signature=179525311196616BD8E1381759B0E5F81A9E91B5.C4A50E44059FEBCC6BBC78E3B3A4E0E0065777&amp;key=ik0</value>
-    </entry>
-    <entry key="StreamingMediaPlayerTest-testHTTP_MPEG4SP_AAC_Video2">
-        <value>http://redirector.gvt1.com/videoplayback?id=c80658495af60617&amp;itag=17&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=70E979A621001201BC18622BDBF914FA870BDA40.6E78890B80F4A33A18835F775B1FF64F0A4D0003&amp;key=ik0&amp;user=android-device-test</value>
-    </entry>
-    <entry key="StreamingMediaPlayerTest-testHTTP_MPEG4SP_AAC_Video1">
-        <value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=17&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=837198AAADF6F36BA6B2D324F690A7C5B7AFE3FF.7138CE5E36D718220726C1FC305497FF2D082249&amp;key=ik0&amp;user=android-device-test</value>
-    </entry>
-    <entry key="MediaCodecCapabilitiesTest-testAvcBaseline12">
+    <entry key="media_codec_capabilities_test_avc_baseline12">
         <value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=160&amp;source=youtube&amp;user=android-device-test&amp;sparams=ip,ipbits,expire,id,itag,source,user&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;signature=9EDCA0B395B8A949C511FD5E59B9F805CFF797FD.702DE9BA7AF96785FD6930AD2DD693A0486C880E&amp;key=ik0</value>
     </entry>
-    <entry key="DecoderTest-AUDIO_URL">
-        <value>http://redirector.gvt1.com/videoplayback?id=c80658495af60617&amp;itag=18&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=46A04ED550CA83B79B60060BA80C79FDA5853D26.49582D382B4A9AFAA163DED38D2AE531D85603C0&amp;key=ik0&amp;user=android-device-test</value>
-    </entry>
-    <entry key="MediaCodecCapabilitiesTest-testAvcBaseline30">
+    <entry key="media_codec_capabilities_test_avc_baseline30">
         <value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=18&amp;source=youtube&amp;user=android-device-test&amp;sparams=ip,ipbits,expire,id,itag,source,user&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;signature=7DCDE3A6594D0B91A27676A3CDC3A87B149F82EA.7A83031734CB1EDCE06766B6228842F954927960&amp;key=ik0</value>
     </entry>
-    <entry key="MediaCodecCapabilitiesTest-testAvcHigh40">
+    <entry key="media_codec_capabilities_test_avc_high31">
+        <value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=22&amp;source=youtube&amp;user=android-device-test&amp;sparams=ip,ipbits,expire,id,itag,source,user&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;signature=179525311196616BD8E1381759B0E5F81A9E91B5.C4A50E44059FEBCC6BBC78E3B3A4E0E0065777&amp;key=ik0</value>
+    </entry>
+    <entry key="media_codec_capabilities_test_avc_high40">
         <value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=137&amp;source=youtube&amp;user=android-device-test&amp;sparams=ip,ipbits,expire,id,itag,source,user&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;signature=B0976085596DD42DEA3F08307F76587241CB132B.043B719C039E8B92F45391ADC0BE3665E2332930&amp;key=ik0</value>
     </entry>
-    <entry key="StreamingMediaPlayerTest-testHTTP_H263_AMR_Video2">
+    <entry key="streaming_media_player_test_http_h263_amr_video1">
+        <value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=13&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=5729247E22691EBB3E804DDD523EC42DC17DD8CE.443B81C1E8E6D64E4E1555F568BA46C206507D78&amp;key=ik0&amp;user=android-device-test</value>
+    </entry>
+    <entry key="streaming_media_player_test_http_h263_amr_video2">
         <value>http://redirector.gvt1.com/videoplayback?id=c80658495af60617&amp;itag=13&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=508D82AB36939345BF6B8D0623CB6CABDD9C64C3.9B3336A96846DF38E5343C46AA57F6CF2956E427&amp;key=ik0&amp;user=android-device-test</value>
     </entry>
-    <entry key="StreamingMediaPlayerTest-testHTTP_H263_AMR_Video1">
-        <value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=13&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=5729247E22691EBB3E804DDD523EC42DC17DD8CE.443B81C1E8E6D64E4E1555F568BA46C206507D78&amp;key=ik0&amp;user=android-device-test</value>
+    <entry key="streaming_media_player_test_http_h264_base_aac_video1">
+        <value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=18&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=667AEEF54639926662CE62361400B8F8C1753B3F.15F46C382C68A9F121BA17BF1F56BEDEB4B06091&amp;key=ik0&amp;user=android-device-test</value>
+    </entry>
+    <entry key="streaming_media_player_test_http_h264_base_aac_video2">
+        <value>http://redirector.gvt1.com/videoplayback?id=c80658495af60617&amp;itag=18&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=46A04ED550CA83B79B60060BA80C79FDA5853D26.49582D382B4A9AFAA163DED38D2AE531D85603C0&amp;key=ik0&amp;user=android-device-test</value>
+    </entry>
+    <entry key="streaming_media_player_test_http_mpeg4_sp_aac_video1">
+        <value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=17&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=837198AAADF6F36BA6B2D324F690A7C5B7AFE3FF.7138CE5E36D718220726C1FC305497FF2D082249&amp;key=ik0&amp;user=android-device-test</value>
+    </entry>
+    <entry key="streaming_media_player_test_http_mpeg4_sp_aac_video2">
+        <value>http://redirector.gvt1.com/videoplayback?id=c80658495af60617&amp;itag=17&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=70E979A621001201BC18622BDBF914FA870BDA40.6E78890B80F4A33A18835F775B1FF64F0A4D0003&amp;key=ik0&amp;user=android-device-test</value>
     </entry>
 </dynamicConfig>
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_525_lr_sdr_mpeg2.mp4 b/tests/tests/media/res/raw/color_176x144_bt601_525_lr_sdr_mpeg2.mp4
new file mode 100644
index 0000000..0edf5c4
--- /dev/null
+++ b/tests/tests/media/res/raw/color_176x144_bt601_525_lr_sdr_mpeg2.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_bt601_625_lr_sdr_mpeg2.mp4 b/tests/tests/media/res/raw/color_176x144_bt601_625_lr_sdr_mpeg2.mp4
new file mode 100644
index 0000000..a16fc6f
--- /dev/null
+++ b/tests/tests/media/res/raw/color_176x144_bt601_625_lr_sdr_mpeg2.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_bt709_lr_sdr_mpeg2.mp4 b/tests/tests/media/res/raw/color_176x144_bt709_lr_sdr_mpeg2.mp4
new file mode 100644
index 0000000..3385ed1
--- /dev/null
+++ b/tests/tests/media/res/raw/color_176x144_bt709_lr_sdr_mpeg2.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/res/raw/color_176x144_srgb_lr_sdr_mpeg2.mp4 b/tests/tests/media/res/raw/color_176x144_srgb_lr_sdr_mpeg2.mp4
new file mode 100644
index 0000000..9de85c7
--- /dev/null
+++ b/tests/tests/media/res/raw/color_176x144_srgb_lr_sdr_mpeg2.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..9125f87 100755
--- a/tests/tests/media/src/android/media/cts/DecoderTest.java
+++ b/tests/tests/media/src/android/media/cts/DecoderTest.java
@@ -38,6 +38,7 @@
 import android.net.Uri;
 
 import com.android.compatibility.common.util.DeviceReportLog;
+import com.android.compatibility.common.util.DynamicConfigDeviceSide;
 import com.android.compatibility.common.util.ResultType;
 import com.android.compatibility.common.util.ResultUnit;
 
@@ -72,20 +73,11 @@
     private MediaCodecTunneledPlayer mMediaCodecPlayer;
     private static final int SLEEP_TIME_MS = 1000;
     private static final long PLAY_TIME_MS = TimeUnit.MILLISECONDS.convert(1, TimeUnit.MINUTES);
-    private static final Uri AUDIO_URL = Uri.parse(
-            "http://redirector.c.youtube.com/videoplayback?id=c80658495af60617"
-                + "&itag=18&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
-                + "&sparams=ip,ipbits,expire,id,itag,source"
-                + "&signature=46A04ED550CA83B79B60060BA80C79FDA5853D26."
-                + "49582D382B4A9AFAA163DED38D2AE531D85603C0"
-                + "&key=ik0&user=android-device-test");  // H.264 Base + AAC
-    private static final Uri VIDEO_URL = Uri.parse(
-            "http://redirector.c.youtube.com/videoplayback?id=c80658495af60617"
-                + "&itag=18&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
-                + "&sparams=ip,ipbits,expire,id,itag,source"
-                + "&signature=46A04ED550CA83B79B60060BA80C79FDA5853D26."
-                + "49582D382B4A9AFAA163DED38D2AE531D85603C0"
-                + "&key=ik0&user=android-device-test");  // H.264 Base + AAC
+
+    private static final String AUDIO_URL_KEY = "decoder_test_audio_url";
+    private static final String VIDEO_URL_KEY = "decoder_test_video_url";
+    private static final String MODULE_NAME = "CtsMediaTestCases";
+    private DynamicConfigDeviceSide dynamicConfig;
 
     @Override
     protected void setUp() throws Exception {
@@ -109,6 +101,8 @@
         }
         bis.close();
         masterFd.close();
+
+        dynamicConfig = new DynamicConfigDeviceSide(MODULE_NAME);
     }
 
     @Override
@@ -297,12 +291,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 +306,87 @@
                 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 */);
+    }
+
+    /**
+     * Test ColorAspects of all the MPEG2 decoders if avaiable. Decoders should
+     * handle the colors aspects presented in both the mp4 atom 'colr' and Sequence
+     * in the bitstream correctly. The following table lists the color aspects
+     * contained in the color box and SeqInfo for the test stream.
+     * P = primaries, T = transfer, M = coeffs, R = range. '-' means
+     * empty value.
+     *                                       |     colr     |    SeqInfo
+     * -------------------------------------------------------------------
+     *         File Name                     |  P  T  M  R  |  P  T  M  R
+     * -------------------------------------------------------------------
+     *  color_176x144_bt709_lr_sdr_mpeg2     |  1  1  1  0  |  -  -  -  -
+     *  color_176x144_bt601_625_lr_sdr_mpeg2 |  1  6  6  0  |  5  2  2  0
+     *  color_176x144_bt601_525_lr_sdr_mpeg2 |  6  5  4  0  |  2  6  6  0
+     *  color_176x144_srgb_lr_sdr_mpeg2      |  2  0  2  0  |  1  13 1  0
+     */
+    public void testMPEG2ColorAspectsTV() throws Exception {
+        testColorAspects(
+                R.raw.color_176x144_bt709_lr_sdr_mpeg2, 1 /* testId */,
+                MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT709,
+                MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
+        testColorAspects(
+                R.raw.color_176x144_bt601_625_lr_sdr_mpeg2, 2 /* testId */,
+                MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT601_PAL,
+                MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
+        testColorAspects(
+                R.raw.color_176x144_bt601_525_lr_sdr_mpeg2, 3 /* testId */,
+                MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT601_NTSC,
+                MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
+        testColorAspects(
+                R.raw.color_176x144_srgb_lr_sdr_mpeg2, 4 /* testId */,
+                MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT709,
+                2 /* MediaFormat.COLOR_TRANSFER_SRGB */);
     }
 
     private void testColorAspects(
@@ -2666,8 +2740,10 @@
         mMediaCodecPlayer = new MediaCodecTunneledPlayer(
                 getActivity().getSurfaceHolder(), true, am.generateAudioSessionId());
 
-        mMediaCodecPlayer.setAudioDataSource(AUDIO_URL, null);
-        mMediaCodecPlayer.setVideoDataSource(VIDEO_URL, null);
+        Uri audioUri = Uri.parse(dynamicConfig.getValue(AUDIO_URL_KEY));
+        Uri videoUri = Uri.parse(dynamicConfig.getValue(VIDEO_URL_KEY));
+        mMediaCodecPlayer.setAudioDataSource(audioUri, null);
+        mMediaCodecPlayer.setVideoDataSource(videoUri, null);
         assertTrue("MediaCodecPlayer.start() failed!", mMediaCodecPlayer.start());
         assertTrue("MediaCodecPlayer.prepare() failed!", mMediaCodecPlayer.prepare());
 
@@ -2706,8 +2782,10 @@
         mMediaCodecPlayer = new MediaCodecTunneledPlayer(
                 getActivity().getSurfaceHolder(), true, am.generateAudioSessionId());
 
-        mMediaCodecPlayer.setAudioDataSource(AUDIO_URL, null);
-        mMediaCodecPlayer.setVideoDataSource(VIDEO_URL, null);
+        Uri audioUri = Uri.parse(dynamicConfig.getValue(AUDIO_URL_KEY));
+        Uri videoUri = Uri.parse(dynamicConfig.getValue(VIDEO_URL_KEY));
+        mMediaCodecPlayer.setAudioDataSource(audioUri, null);
+        mMediaCodecPlayer.setVideoDataSource(videoUri, null);
         assertTrue("MediaCodecPlayer.start() failed!", mMediaCodecPlayer.start());
         assertTrue("MediaCodecPlayer.prepare() failed!", mMediaCodecPlayer.prepare());
 
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java b/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
index 5b2936b..446cd3c 100644
--- a/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
@@ -36,6 +36,8 @@
 import android.os.Build;
 import android.util.Log;
 
+import com.android.compatibility.common.util.DynamicConfigDeviceSide;
+
 import java.io.IOException;
 import java.util.HashSet;
 import java.util.Set;
@@ -61,6 +63,21 @@
     private final MediaCodecInfo[] mAllInfos =
             mAllCodecs.getCodecInfos();
 
+    private static final String AVC_BASELINE_12_KEY =
+            "media_codec_capabilities_test_avc_baseline12";
+    private static final String AVC_BASELINE_30_KEY =
+            "media_codec_capabilities_test_avc_baseline30";
+    private static final String AVC_HIGH_31_KEY = "media_codec_capabilities_test_avc_high31";
+    private static final String AVC_HIGH_40_KEY = "media_codec_capabilities_test_avc_high40";
+    private static final String MODULE_NAME = "CtsMediaTestCases";
+    private DynamicConfigDeviceSide dynamicConfig;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        dynamicConfig = new DynamicConfigDeviceSide(MODULE_NAME);
+    }
+
     // Android device implementations with H.264 encoders, MUST support Baseline Profile Level 3.
     // SHOULD support Main Profile/ Level 4, if supported the device must also support Main
     // Profile/Level 4 decoding.
@@ -172,13 +189,8 @@
             return; // skip
         }
 
-        playVideoWithRetries("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
-                + "&itag=160&source=youtube&user=android-device-test"
-                + "&sparams=ip,ipbits,expire,id,itag,source,user"
-                + "&ip=0.0.0.0&ipbits=0&expire=19000000000"
-                + "&signature=9EDCA0B395B8A949C511FD5E59B9F805CFF797FD."
-                + "702DE9BA7AF96785FD6930AD2DD693A0486C880E"
-                + "&key=ik0", 256, 144, PLAY_TIME_MS);
+        String urlString = dynamicConfig.getValue(AVC_BASELINE_12_KEY);
+        playVideoWithRetries(urlString, 256, 144, PLAY_TIME_MS);
     }
 
     public void testAvcBaseline30() throws Exception {
@@ -186,13 +198,8 @@
             return; // skip
         }
 
-        playVideoWithRetries("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
-                + "&itag=18&source=youtube&user=android-device-test"
-                + "&sparams=ip,ipbits,expire,id,itag,source,user"
-                + "&ip=0.0.0.0&ipbits=0&expire=19000000000"
-                + "&signature=7DCDE3A6594D0B91A27676A3CDC3A87B149F82EA."
-                + "7A83031734CB1EDCE06766B6228842F954927960"
-                + "&key=ik0", 640, 360, PLAY_TIME_MS);
+        String urlString = dynamicConfig.getValue(AVC_BASELINE_30_KEY);
+        playVideoWithRetries(urlString, 640, 360, PLAY_TIME_MS);
     }
 
     public void testAvcHigh31() throws Exception {
@@ -200,13 +207,8 @@
             return; // skip
         }
 
-        playVideoWithRetries("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
-                + "&itag=22&source=youtube&user=android-device-test"
-                + "&sparams=ip,ipbits,expire,id,itag,source,user"
-                + "&ip=0.0.0.0&ipbits=0&expire=19000000000"
-                + "&signature=179525311196616BD8E1381759B0E5F81A9E91B5."
-                + "C4A50E44059FEBCC6BBC78E3B3A4E0E0065777"
-                + "&key=ik0", 1280, 720, PLAY_TIME_MS);
+        String urlString = dynamicConfig.getValue(AVC_HIGH_31_KEY);
+        playVideoWithRetries(urlString, 1280, 720, PLAY_TIME_MS);
     }
 
     public void testAvcHigh40() throws Exception {
@@ -218,13 +220,8 @@
             return;
         }
 
-        playVideoWithRetries("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
-                + "&itag=137&source=youtube&user=android-device-test"
-                + "&sparams=ip,ipbits,expire,id,itag,source,user"
-                + "&ip=0.0.0.0&ipbits=0&expire=19000000000"
-                + "&signature=B0976085596DD42DEA3F08307F76587241CB132B."
-                + "043B719C039E8B92F45391ADC0BE3665E2332930"
-                + "&key=ik0", 1920, 1080, PLAY_TIME_MS);
+        String urlString = dynamicConfig.getValue(AVC_HIGH_40_KEY);
+        playVideoWithRetries(urlString, 1920, 1080, PLAY_TIME_MS);
     }
 
     public void testHevcMain1() throws Exception {
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/media/src/android/media/cts/StreamingMediaPlayerTest.java b/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java
index 0464cb6..0db2e05 100644
--- a/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java
+++ b/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java
@@ -26,6 +26,8 @@
 import android.util.Log;
 import android.webkit.cts.CtsTestServer;
 
+import com.android.compatibility.common.util.DynamicConfigDeviceSide;
+
 import java.io.IOException;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -35,8 +37,29 @@
 public class StreamingMediaPlayerTest extends MediaPlayerTestBase {
     private static final String TAG = "StreamingMediaPlayerTest";
 
+    private static final String HTTP_H263_AMR_VIDEO_1_KEY =
+            "streaming_media_player_test_http_h263_amr_video1";
+    private static final String HTTP_H263_AMR_VIDEO_2_KEY =
+            "streaming_media_player_test_http_h263_amr_video2";
+    private static final String HTTP_H264_BASE_AAC_VIDEO_1_KEY =
+            "streaming_media_player_test_http_h264_base_aac_video1";
+    private static final String HTTP_H264_BASE_AAC_VIDEO_2_KEY =
+            "streaming_media_player_test_http_h264_base_aac_video2";
+    private static final String HTTP_MPEG4_SP_AAC_VIDEO_1_KEY =
+            "streaming_media_player_test_http_mpeg4_sp_aac_video1";
+    private static final String HTTP_MPEG4_SP_AAC_VIDEO_2_KEY =
+            "streaming_media_player_test_http_mpeg4_sp_aac_video2";
+    private static final String MODULE_NAME = "CtsMediaTestCases";
+    private DynamicConfigDeviceSide dynamicConfig;
+
     private CtsTestServer mServer;
 
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        dynamicConfig = new DynamicConfigDeviceSide(MODULE_NAME);
+    }
+
 /* RTSP tests are more flaky and vulnerable to network condition.
    Disable until better solution is available
     // Streaming RTSP video from YouTube
@@ -73,12 +96,8 @@
             return; // skip
         }
 
-        playVideoTest("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
-                + "&itag=13&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
-                + "&sparams=ip,ipbits,expire,id,itag,source"
-                + "&signature=5729247E22691EBB3E804DDD523EC42DC17DD8CE"
-                + ".443B81C1E8E6D64E4E1555F568BA46C206507D78"
-                + "&key=ik0&user=android-device-test", 176, 144);
+        String urlString = dynamicConfig.getValue(HTTP_H263_AMR_VIDEO_1_KEY);
+        playVideoTest(urlString, 176, 144);
     }
 
     public void testHTTP_H263_AMR_Video2() throws Exception {
@@ -86,12 +105,8 @@
             return; // skip
         }
 
-        playVideoTest("http://redirector.c.youtube.com/videoplayback?id=c80658495af60617"
-                + "&itag=13&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
-                + "&sparams=ip,ipbits,expire,id,itag,source"
-                + "&signature=508D82AB36939345BF6B8D0623CB6CABDD9C64C3"
-                + ".9B3336A96846DF38E5343C46AA57F6CF2956E427"
-                + "&key=ik0&user=android-device-test", 176, 144);
+        String urlString = dynamicConfig.getValue(HTTP_H263_AMR_VIDEO_2_KEY);
+        playVideoTest(urlString, 176, 144);
     }
 
     public void testHTTP_MPEG4SP_AAC_Video1() throws Exception {
@@ -99,12 +114,8 @@
             return; // skip
         }
 
-        playVideoTest("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
-                + "&itag=17&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
-                + "&sparams=ip,ipbits,expire,id,itag,source"
-                + "&signature=837198AAADF6F36BA6B2D324F690A7C5B7AFE3FF"
-                + ".7138CE5E36D718220726C1FC305497FF2D082249"
-                + "&key=ik0&user=android-device-test", 176, 144);
+        String urlString = dynamicConfig.getValue(HTTP_MPEG4_SP_AAC_VIDEO_1_KEY);
+        playVideoTest(urlString, 176, 144);
     }
 
     public void testHTTP_MPEG4SP_AAC_Video2() throws Exception {
@@ -112,12 +123,8 @@
             return; // skip
         }
 
-        playVideoTest("http://redirector.c.youtube.com/videoplayback?id=c80658495af60617"
-                + "&itag=17&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
-                + "&sparams=ip,ipbits,expire,id,itag,source"
-                + "&signature=70E979A621001201BC18622BDBF914FA870BDA40"
-                + ".6E78890B80F4A33A18835F775B1FF64F0A4D0003"
-                + "&key=ik0&user=android-device-test", 176, 144);
+        String urlString = dynamicConfig.getValue(HTTP_MPEG4_SP_AAC_VIDEO_2_KEY);
+        playVideoTest(urlString, 176, 144);
     }
 
     public void testHTTP_H264Base_AAC_Video1() throws Exception {
@@ -125,12 +132,8 @@
             return; // skip
         }
 
-        playVideoTest("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
-                + "&itag=18&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
-                + "&sparams=ip,ipbits,expire,id,itag,source"
-                + "&signature=667AEEF54639926662CE62361400B8F8C1753B3F"
-                + ".15F46C382C68A9F121BA17BF1F56BEDEB4B06091"
-                + "&key=ik0&user=android-device-test", 640, 360);
+        String urlString = dynamicConfig.getValue(HTTP_H264_BASE_AAC_VIDEO_1_KEY);
+        playVideoTest(urlString, 640, 360);
     }
 
     public void testHTTP_H264Base_AAC_Video2() throws Exception {
@@ -138,12 +141,8 @@
             return; // skip
         }
 
-        playVideoTest("http://redirector.c.youtube.com/videoplayback?id=c80658495af60617"
-                + "&itag=18&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
-                + "&sparams=ip,ipbits,expire,id,itag,source"
-                + "&signature=46A04ED550CA83B79B60060BA80C79FDA5853D26"
-                + ".49582D382B4A9AFAA163DED38D2AE531D85603C0"
-                + "&key=ik0&user=android-device-test", 640, 360);
+        String urlString = dynamicConfig.getValue(HTTP_H264_BASE_AAC_VIDEO_2_KEY);
+        playVideoTest(urlString, 640, 360);
     }
 
     // Streaming HLS video from YouTube
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/permission/src/android/permission/cts/FileSystemPermissionTest.java b/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
index 617f0ab..5c50f86 100644
--- a/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
@@ -29,7 +29,6 @@
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileFilter;
-import java.io.FilenameFilter;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
@@ -403,35 +402,6 @@
         assertFalse(f.canExecute());
     }
 
-    @MediumTest
-    public void testDeviceTreeCpuCurrent() throws Exception {
-        String arch = System.getProperty("os.arch");
-        String[] osVersion = System.getProperty("os.version").split("\\.");
-        /*
-         * Perform the test for only arm-based architecture and
-         * kernel version 3.10 and above.
-         */
-        if (!arch.contains("arm") ||
-            Integer.parseInt(osVersion[0]) < 2 ||
-            (Integer.parseInt(osVersion[0]) == 3 &&
-             Integer.parseInt(osVersion[1]) < 10))
-            return;
-        final File f = new File("/proc/device-tree/cpus");
-        if (!f.exists())
-            return;
-        String[] dir = f.list(new FilenameFilter() {
-            @Override
-            public boolean accept(File pathname, String name) {
-                return (pathname.isDirectory() && name.matches("cpu@[0-9]+"));
-            }
-        });
-
-        for(String cpuDir : dir) {
-            File fCpu = new File(cpuDir + "/current");
-            assertTrue(f.canRead());
-        }
-    }
-
     private static boolean isDirectoryWritable(File directory) {
         File toCreate = new File(directory, "hello");
         try {
diff --git a/tests/tests/permission2/res/raw/android_manifest.xml b/tests/tests/permission2/res/raw/android_manifest.xml
index 4ebe527..e53f5c0 100644
--- a/tests/tests/permission2/res/raw/android_manifest.xml
+++ b/tests/tests/permission2/res/raw/android_manifest.xml
@@ -1264,6 +1264,11 @@
     <permission android:name="android.permission.CONNECTIVITY_INTERNAL"
                 android:protectionLevel="signature|privileged" />
 
+    <!-- Allows an internal user to use restricted Networks.
+         @hide -->
+    <permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"
+        android:protectionLevel="signature|privileged" />
+
     <!-- Allows a system application to access hardware packet offload capabilities.
          @hide -->
     <permission android:name="android.permission.PACKET_KEEPALIVE_OFFLOAD"
@@ -2510,11 +2515,10 @@
     <permission android:name="android.permission.CONTROL_WIFI_DISPLAY"
                 android:protectionLevel="signature" />
 
-    <!-- Allows an application to control the color transforms applied to
-         displays system-wide.
+    <!-- Allows an application to control the color modes set for displays system-wide.
          <p>Not for use by third-party applications.</p>
          @hide -->
-    <permission android:name="android.permission.CONFIGURE_DISPLAY_COLOR_TRANSFORM"
+    <permission android:name="android.permission.CONFIGURE_DISPLAY_COLOR_MODE"
                 android:protectionLevel="signature" />
 
     <!-- @SystemApi Allows an application to control VPN.
diff --git a/tests/tests/security/src/android/security/cts/SELinuxTest.java b/tests/tests/security/src/android/security/cts/SELinuxTest.java
index 24ad020..1733aa5 100644
--- a/tests/tests/security/src/android/security/cts/SELinuxTest.java
+++ b/tests/tests/security/src/android/security/cts/SELinuxTest.java
@@ -69,8 +69,11 @@
         assertEquals(getFileContext("/data"), "u:object_r:system_data_file:s0");
         assertEquals(getFileContext("/data/app"), "u:object_r:apk_data_file:s0");
         assertEquals(getFileContext("/data/local/tmp"), "u:object_r:shell_data_file:s0");
-        assertEquals(getFileContext("/cache"), "u:object_r:cache_file:s0");
         assertEquals(getFileContext("/sys"), "u:object_r:sysfs:s0");
+        File dir = new File("/cache");
+        if (dir.exists()) {
+            assertEquals(getFileContext("/cache"), "u:object_r:cache_file:s0");
+        }
     }
 
     private static final native String getFileContext(String path);
diff --git a/tests/tests/shortcutmanager/Android.mk b/tests/tests/shortcutmanager/Android.mk
new file mode 100755
index 0000000..245f0f6
--- /dev/null
+++ b/tests/tests/shortcutmanager/Android.mk
@@ -0,0 +1,46 @@
+# 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_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    android-support-v4 \
+    mockito-target \
+    ctsdeviceutil \
+    ctstestrunner \
+    ub-uiautomator \
+    ShortcutManagerTestUtils
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+    $(call all-java-files-under, common/src)
+
+LOCAL_PACKAGE_NAME := CtsShortcutManagerTestCases
+
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_SDK_VERSION := test_current
+
+include $(BUILD_CTS_PACKAGE)
+#include $(BUILD_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/shortcutmanager/AndroidManifest.xml b/tests/tests/shortcutmanager/AndroidManifest.xml
new file mode 100755
index 0000000..7be2135
--- /dev/null
+++ b/tests/tests/shortcutmanager/AndroidManifest.xml
@@ -0,0 +1,55 @@
+<?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.content.pm.cts.shortcutmanager"
+    android:sharedUserId="android.content.pm.cts.shortcutmanager.packages">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+
+        <activity android:name="android.content.pm.cts.shortcutmanager.MyActivity" />
+
+        <activity-alias android:name="non_main"
+            android:targetActivity="android.content.pm.cts.shortcutmanager.MyActivity" >
+        </activity-alias>
+        <activity-alias android:name="disabled_main"
+            android:targetActivity="android.content.pm.cts.shortcutmanager.MyActivity"
+            android:enabled="false">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity-alias>
+        <activity-alias android:name="main"
+            android:targetActivity="android.content.pm.cts.shortcutmanager.MyActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity-alias>
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.content.pm.cts.shortcutmanager"
+        android:label="CTS tests for ShortcutManager">
+        <meta-data android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+
+</manifest>
+
diff --git a/tests/tests/shortcutmanager/AndroidTest.xml b/tests/tests/shortcutmanager/AndroidTest.xml
new file mode 100644
index 0000000..31b6b36
--- /dev/null
+++ b/tests/tests/shortcutmanager/AndroidTest.xml
@@ -0,0 +1,33 @@
+<?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 ShortcutManager CTS test cases">
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.ApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsShortcutManagerTestCases.apk" />
+        <option name="test-file-name" value="CtsShortcutManagerPackage1.apk" />
+        <option name="test-file-name" value="CtsShortcutManagerPackage2.apk" />
+        <option name="test-file-name" value="CtsShortcutManagerPackage3.apk" />
+        <option name="test-file-name" value="CtsShortcutManagerPackage4.apk" />
+        <option name="test-file-name" value="CtsShortcutManagerLauncher1.apk" />
+        <option name="test-file-name" value="CtsShortcutManagerLauncher2.apk" />
+        <option name="test-file-name" value="CtsShortcutManagerLauncher3.apk" />
+        <option name="test-file-name" value="CtsShortcutManagerLauncher4.apk" />
+        <option name="test-file-name" value="CtsShortcutManagerThrottlingTest.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.content.pm.cts.shortcutmanager" />
+    </test>
+</configuration>
\ No newline at end of file
diff --git a/tests/tests/shortcutmanager/common/src/android.content.pm.cts.shortcutmanager.common/Constants.java b/tests/tests/shortcutmanager/common/src/android.content.pm.cts.shortcutmanager.common/Constants.java
new file mode 100644
index 0000000..84f92d7
--- /dev/null
+++ b/tests/tests/shortcutmanager/common/src/android.content.pm.cts.shortcutmanager.common/Constants.java
@@ -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.
+ */
+package android.content.pm.cts.shortcutmanager.common;
+
+public class Constants {
+    public static final String ACTION_THROTTLING_TEST =
+            "android.content.pm.cts.shortcutmanager.ACTION_THROTTLING_TEST";
+    public static final String ACTION_THROTTLING_REPLY =
+            "android.content.pm.cts.shortcutmanager.ACTION_THROTTLING_REPLY";
+
+    public static final String EXTRA_METHOD = "method";
+    public static final String EXTRA_REPLY_ACTION = "reply_action";
+
+    public static final String TEST_SET_DYNAMIC_SHORTCUTS = "testSetDynamicShortcuts";
+    public static final String TEST_ADD_DYNAMIC_SHORTCUTS = "testAddDynamicShortcuts";
+    public static final String TEST_UPDATE_SHORTCUTS = "testUpdateShortcuts";
+
+    public static final String TEST_ACTIVITY_UNTHROTTLED = "testActivityUnthrottled";
+    public static final String TEST_FG_SERVICE_UNTHROTTLED = "testFgServiceUnthrottled";
+    public static final String TEST_BG_SERVICE_THROTTLED = "testBgServiceThrottled";
+}
diff --git a/tests/tests/shortcutmanager/packages/Android.mk b/tests/tests/shortcutmanager/packages/Android.mk
new file mode 100644
index 0000000..3d02f9c
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/Android.mk
@@ -0,0 +1,17 @@
+# 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 $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/shortcutmanager/packages/launchermanifest/Android.mk b/tests/tests/shortcutmanager/packages/launchermanifest/Android.mk
new file mode 100644
index 0000000..acdb149
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/launchermanifest/Android.mk
@@ -0,0 +1,75 @@
+# 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 := CtsShortcutManagerLauncher1
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
+
+LOCAL_SDK_VERSION := current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_AAPT_FLAGS += --rename-manifest-package android.content.pm.cts.shortcutmanager.packages.launcher1
+
+include $(BUILD_CTS_PACKAGE)
+
+#-----------------------------------------------------------
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsShortcutManagerLauncher2
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
+
+LOCAL_SDK_VERSION := current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_AAPT_FLAGS += --rename-manifest-package android.content.pm.cts.shortcutmanager.packages.launcher2
+
+include $(BUILD_CTS_PACKAGE)
+
+#-----------------------------------------------------------
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsShortcutManagerLauncher3
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
+
+LOCAL_SDK_VERSION := current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_AAPT_FLAGS += --rename-manifest-package android.content.pm.cts.shortcutmanager.packages.launcher3
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/shortcutmanager/packages/launchermanifest/AndroidManifest.xml b/tests/tests/shortcutmanager/packages/launchermanifest/AndroidManifest.xml
new file mode 100755
index 0000000..4290c7f
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/launchermanifest/AndroidManifest.xml
@@ -0,0 +1,48 @@
+<?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.content.pm.cts.shortcutmanager.packages"
+    android:sharedUserId="android.content.pm.cts.shortcutmanager.packages">
+
+    <application>
+        <activity android:name="Launcher">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.HOME" />
+            </intent-filter>
+        </activity>
+        <activity-alias android:name="Launcher2"
+                android:targetActivity="Launcher">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.HOME" />
+            </intent-filter>
+        </activity-alias>
+        <activity-alias android:name="Launcher3"
+            android:targetActivity="Launcher">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.HOME" />
+            </intent-filter>
+        </activity-alias>
+    </application>
+</manifest>
+
diff --git a/tests/tests/shortcutmanager/packages/launchermanifest_nonshared/Android.mk b/tests/tests/shortcutmanager/packages/launchermanifest_nonshared/Android.mk
new file mode 100644
index 0000000..4aebb74
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/launchermanifest_nonshared/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 := CtsShortcutManagerLauncher4
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
+
+LOCAL_SDK_VERSION := current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_AAPT_FLAGS += --rename-manifest-package android.content.pm.cts.shortcutmanager.packages.launcher4
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/shortcutmanager/packages/launchermanifest_nonshared/AndroidManifest.xml b/tests/tests/shortcutmanager/packages/launchermanifest_nonshared/AndroidManifest.xml
new file mode 100755
index 0000000..c43d574
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/launchermanifest_nonshared/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?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.content.pm.cts.shortcutmanager.packages">
+
+    <application>
+        <activity android:name="Launcher">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.HOME" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
+
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/Android.mk b/tests/tests/shortcutmanager/packages/packagemanifest/Android.mk
new file mode 100644
index 0000000..458cb4e
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/Android.mk
@@ -0,0 +1,74 @@
+# 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 := CtsShortcutManagerPackage1
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
+
+LOCAL_SDK_VERSION := current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_AAPT_FLAGS += --rename-manifest-package android.content.pm.cts.shortcutmanager.packages.package1
+
+include $(BUILD_CTS_PACKAGE)
+
+#-----------------------------------------------------------
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsShortcutManagerPackage2
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
+
+LOCAL_SDK_VERSION := current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_AAPT_FLAGS += --rename-manifest-package android.content.pm.cts.shortcutmanager.packages.package2
+
+include $(BUILD_CTS_PACKAGE)
+
+#-----------------------------------------------------------
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsShortcutManagerPackage3
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
+
+LOCAL_SDK_VERSION := current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_AAPT_FLAGS += --rename-manifest-package android.content.pm.cts.shortcutmanager.packages.package3
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/AndroidManifest.xml b/tests/tests/shortcutmanager/packages/packagemanifest/AndroidManifest.xml
new file mode 100755
index 0000000..18a118e
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/AndroidManifest.xml
@@ -0,0 +1,156 @@
+<?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.content.pm.cts.shortcutmanager.packages"
+    android:sharedUserId="android.content.pm.cts.shortcutmanager.packages">
+
+    <application>
+        <activity android:name="Launcher"
+            android:enabled="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="android.intent.category.HOME" />
+            </intent-filter>
+        </activity>
+
+        <activity-alias android:name="Launcher2"
+            android:targetActivity="Launcher">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity-alias>
+
+        <activity-alias android:name="Launcher3"
+            android:targetActivity="Launcher">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity-alias>
+
+        <activity-alias android:name="Launcher4"
+            android:targetActivity="Launcher">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity-alias>
+
+        <activity-alias android:name="Launcher5"
+            android:targetActivity="Launcher">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity-alias>
+
+        <activity-alias android:name="Launcher_no_main_1"
+            android:targetActivity="Launcher">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+            </intent-filter>
+        </activity-alias>
+
+        <activity-alias android:name="Launcher_no_main_2"
+            android:targetActivity="Launcher">
+            <intent-filter>
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity-alias>
+
+        <activity-alias android:name="Launcher_manifest_1"
+            android:enabled="false"
+            android:targetActivity="Launcher">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+            <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts1"/>
+        </activity-alias>
+
+        <activity-alias android:name="Launcher_manifest_2"
+            android:enabled="false"
+            android:targetActivity="Launcher">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+            <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts2"/>
+        </activity-alias>
+
+        <activity-alias android:name="Launcher_manifest_3"
+            android:enabled="false"
+            android:targetActivity="Launcher">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+            <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts3"/>
+        </activity-alias>
+
+        <activity-alias android:name="Launcher_manifest_4a"
+            android:enabled="false"
+            android:targetActivity="Launcher">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+            <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts4a"/>
+        </activity-alias>
+
+        <activity-alias android:name="Launcher_manifest_4b"
+            android:enabled="false"
+            android:targetActivity="Launcher">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+            <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts4b"/>
+        </activity-alias>
+
+        <activity-alias android:name="Launcher_manifest_error_1"
+            android:enabled="false"
+            android:targetActivity="Launcher">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+            <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcut_error_1"/>
+        </activity-alias>
+        <activity-alias android:name="Launcher_manifest_error_2"
+            android:enabled="false"
+            android:targetActivity="Launcher">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+            <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcut_error_2"/>
+        </activity-alias>
+        <activity-alias android:name="Launcher_manifest_error_3"
+            android:enabled="false"
+            android:targetActivity="Launcher">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+            <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcut_error_3"/>
+        </activity-alias>
+    </application>
+</manifest>
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_1024x4096.png b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_1024x4096.png
new file mode 100644
index 0000000..9b2371d
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_1024x4096.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_16x16.png b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_16x16.png
new file mode 100644
index 0000000..a26da5c
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_16x16.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_16x64.png b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_16x64.png
new file mode 100644
index 0000000..ed049fa
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_16x64.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_32x32.png b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_32x32.png
new file mode 100644
index 0000000..a1e25c1
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_32x32.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_4096x1024.png b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_4096x1024.png
new file mode 100644
index 0000000..8fec244
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_4096x1024.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_4096x4096.png b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_4096x4096.png
new file mode 100644
index 0000000..a42ff4d
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_4096x4096.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_512x512.png b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_512x512.png
new file mode 100644
index 0000000..60304b7
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_512x512.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_64x16.png b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_64x16.png
new file mode 100644
index 0000000..a0983c7
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_64x16.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_64x64.png b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_64x64.png
new file mode 100644
index 0000000..7cc9373
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/black_64x64.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/icon1.png b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/icon1.png
new file mode 100644
index 0000000..64eb294
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/icon1.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/icon2.png b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/icon2.png
new file mode 100644
index 0000000..7502484
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/res/drawable-nodpi/icon2.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/res/values/strings.xml b/tests/tests/shortcutmanager/packages/packagemanifest/res/values/strings.xml
new file mode 100644
index 0000000..068c991
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/res/values/strings.xml
@@ -0,0 +1,37 @@
+<?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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="label1">Shortcut 1</string>
+    <string name="long_label1">Long shortcut label1</string>
+    <string name="disabled_message1">Shortcut 1 is disabled</string>
+
+    <string name="label2">Shortcut 2</string>
+    <string name="long_label2">Long shortcut label2</string>
+    <string name="disabled_message2">Shortcut 2 is disabled</string>
+
+    <string name="label3">Shortcut 3</string>
+    <string name="long_label3">Long shortcut label3</string>
+    <string name="disabled_message3">Shortcut 3 is disabled</string>
+
+    <string name="label4">Shortcut 4</string>
+    <string name="long_label4">Long shortcut label4</string>
+    <string name="disabled_message4">Shortcut 4 is disabled</string>
+
+    <string name="label5">Shortcut 5</string>
+    <string name="long_label5">Long shortcut label5</string>
+    <string name="disabled_message5">Shortcut 5 is disabled</string>
+</resources>
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcut_error_1.xml b/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcut_error_1.xml
new file mode 100644
index 0000000..45e8dde
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcut_error_1.xml
@@ -0,0 +1,30 @@
+<?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.
+-->
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+    <!-- Invalid: tag name shouldn't be capitalized -->
+    <Shortcut
+        android:shortcutId="ms1"
+        android:icon="@drawable/icon1"
+        android:shortcutShortLabel="@string/label1"
+        android:shortcutLongLabel="@string/long_label1"
+        android:shortcutDisabledMessage="@string/disabled_message1">
+        <intent
+            android:action="android.intent.action.VIEW"
+            android:data="http://www.google.com/" />
+        <categories android:name="android.shortcut.conversation" />
+        <categories android:name="android.shortcut.media" />
+    </Shortcut>
+</shortcuts>
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcut_error_2.xml b/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcut_error_2.xml
new file mode 100644
index 0000000..82483fc
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcut_error_2.xml
@@ -0,0 +1,30 @@
+<?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.
+-->
+<!-- Invalid: tag name shouldn't be capitalized -->
+<Shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+    <Shortcut
+        android:shortcutId="ms1"
+        android:icon="@drawable/icon1"
+        android:shortcutShortLabel="@string/label1"
+        android:shortcutLongLabel="@string/long_label1"
+        android:shortcutDisabledMessage="@string/disabled_message1">
+        <intent
+            android:action="android.intent.action.VIEW"
+            android:data="http://www.google.com/" />
+        <categories android:name="android.shortcut.conversation" />
+        <categories android:name="android.shortcut.media" />
+    </Shortcut>
+</Shortcuts>
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcut_error_3.xml b/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcut_error_3.xml
new file mode 100644
index 0000000..b41bcd2
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcut_error_3.xml
@@ -0,0 +1,78 @@
+<?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.
+-->
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+    <!-- Invalid: no ID -->
+    <shortcut
+        android:shortcutShortLabel="@string/label1"
+        >
+        <intent android:action="android.intent.action.VIEW" />
+    </shortcut>
+
+    <!-- Invalid: no short label -->
+    <shortcut
+        android:shortcutId="x1"
+        >
+        <intent android:action="android.intent.action.VIEW" >
+        </intent>
+    </shortcut>
+
+    <!-- Invalid: no Intent -->
+    <shortcut
+        android:shortcutId="manifest-shortcut-3"
+        android:shortcutShortLabel="@string/label1"
+    />
+
+    <!-- Invalid: ID must be literal -->
+    <shortcut
+        android:shortcutId="@string/label1"
+        android:shortcutShortLabel="@string/label1"
+        >
+        <intent android:action="android.intent.action.VIEW" />
+    </shortcut>
+
+    <!-- Valid: disabled shortcut doesn't need an intent -->
+    <shortcut
+        android:shortcutId="disabled1"
+        android:enabled="false"
+        android:shortcutShortLabel="@string/label1"
+    />
+
+    <!-- Valid, but disabled shortcut's intent will be ignored. -->
+    <shortcut
+        android:shortcutId="disabled2"
+        android:enabled="false"
+        android:shortcutShortLabel="@string/label1"
+        >
+        <intent android:action="action4" />
+    </shortcut>
+
+    <!-- Invalid, no intent action (if any of the intents is invalid, the entire shortcut will be invalid.) -->
+    <shortcut
+        android:shortcutId="x1"
+        android:shortcutShortLabel="@string/label1"
+    >
+        <intent android:data="x"/>
+        <intent android:action="actionx"/>
+    </shortcut>
+
+    <shortcut
+        android:shortcutId="valid"
+        android:shortcutShortLabel="@string/label1"
+        >
+        <intent android:action="android.intent.action.VIEW" >
+        </intent>
+    </shortcut>
+</shortcuts>
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcuts1.xml b/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcuts1.xml
new file mode 100644
index 0000000..a3e8472
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcuts1.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+    <shortcut
+        android:shortcutId="ms1"
+        android:icon="@drawable/icon1"
+        android:shortcutShortLabel="@string/label1"
+        android:shortcutLongLabel="@string/long_label1"
+        android:shortcutDisabledMessage="@string/disabled_message1">
+        <intent
+            android:action="android.intent.action.VIEW"
+            android:data="http://www.google.com/" />
+        <categories android:name="android.shortcut.conversation" />
+        <categories android:name="android.shortcut.media" />
+    </shortcut>
+</shortcuts>
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcuts2.xml b/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcuts2.xml
new file mode 100644
index 0000000..7ad15b7
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcuts2.xml
@@ -0,0 +1,45 @@
+<?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.
+-->
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+    <shortcut
+        android:shortcutId="ms21"
+        android:icon="@drawable/black_16x16"
+        android:shortcutShortLabel="@string/label1"
+        android:shortcutLongLabel="@string/long_label1"
+        android:shortcutDisabledMessage="@string/disabled_message1">
+        <intent
+            android:action="android.intent.action.VIEW" />
+        <categories android:name="android.shortcut.conversation" />
+        <categories android:name="android.shortcut.media" />
+    </shortcut>
+    <shortcut
+        android:shortcutId="ms22"
+        android:shortcutShortLabel="@string/label2">
+        <intent android:action="action" />
+        <intent android:action="action2"
+            android:data="data"
+            android:mimeType="a/b"
+            android:targetPackage="pkg"
+            android:targetClass="pkg.class"
+            >
+            <categories android:name="icat1"/>
+            <categories android:name="icat2"/>
+            <extra android:name="key1" android:value="value1" />
+            <extra android:name="key2" android:value="123" />
+            <extra android:name="key3" android:value="true" />
+        </intent>
+    </shortcut>
+</shortcuts>
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcuts3.xml b/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcuts3.xml
new file mode 100644
index 0000000..22f66b3
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcuts3.xml
@@ -0,0 +1,34 @@
+<?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.
+-->
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+    <shortcut
+        android:shortcutId="ms31"
+        android:icon="@drawable/icon1"
+        android:shortcutShortLabel="@string/label1"
+        android:shortcutLongLabel="@string/long_label1"
+        android:shortcutDisabledMessage="@string/disabled_message1">
+        <intent
+            android:action="android.intent.action.VIEW"
+            android:data="http://www.google.com/" />
+        <categories android:name="android.shortcut.conversation" />
+        <categories android:name="android.shortcut.media" />
+    </shortcut>
+    <shortcut
+        android:shortcutId="ms32"
+        android:shortcutShortLabel="@string/label2">
+        <intent android:action="android.intent.action.DIAL" />
+    </shortcut>
+</shortcuts>
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcuts4a.xml b/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcuts4a.xml
new file mode 100644
index 0000000..45d90e1
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcuts4a.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.
+-->
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+    <shortcut
+        android:shortcutId="ms41"
+        android:shortcutShortLabel="@string/label1">
+        <intent android:action="android.intent.action.VIEW" />
+    </shortcut>
+    <shortcut
+        android:shortcutId="ms42"
+        android:shortcutShortLabel="@string/label1">
+        <intent android:action="android.intent.action.VIEW" />
+    </shortcut>
+    <shortcut
+        android:shortcutId="ms43"
+        android:shortcutShortLabel="@string/label1">
+        <intent android:action="android.intent.action.VIEW" />
+    </shortcut>
+</shortcuts>
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcuts4b.xml b/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcuts4b.xml
new file mode 100644
index 0000000..ac4979d
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/res/xml/shortcuts4b.xml
@@ -0,0 +1,36 @@
+<?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.
+-->
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+    <!-- valid, disabled shortcut doesn't need an intent -->
+    <shortcut
+        android:shortcutId="ms41"
+        android:enabled="false"
+        android:shortcutShortLabel="@string/label1"
+        android:shortcutDisabledMessage="@string/disabled_message1">
+    </shortcut>
+    <!-- valid, but disabled shortcuts' intent will be ignored. -->
+    <shortcut
+        android:shortcutId="ms42"
+        android:enabled="false"
+        android:shortcutShortLabel="@string/label1">
+        <intent android:action="myaction" />
+    </shortcut>
+    <shortcut
+        android:shortcutId="ms43"
+        android:enabled="false"
+        android:shortcutShortLabel="@string/label1">
+    </shortcut>
+</shortcuts>
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest_nonshared/Android.mk b/tests/tests/shortcutmanager/packages/packagemanifest_nonshared/Android.mk
new file mode 100644
index 0000000..793fa66
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest_nonshared/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 := CtsShortcutManagerPackage4
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
+
+LOCAL_SDK_VERSION := current
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_AAPT_FLAGS += --rename-manifest-package android.content.pm.cts.shortcutmanager.packages.package4
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest_nonshared/AndroidManifest.xml b/tests/tests/shortcutmanager/packages/packagemanifest_nonshared/AndroidManifest.xml
new file mode 100755
index 0000000..05c9db7
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest_nonshared/AndroidManifest.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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.content.pm.cts.shortcutmanager.packages">
+
+    <application>
+    </application>
+</manifest>
+
diff --git a/tests/tests/shortcutmanager/packages/src/android/content/pm/cts/shortcutmanager/packages/Launcher.java b/tests/tests/shortcutmanager/packages/src/android/content/pm/cts/shortcutmanager/packages/Launcher.java
new file mode 100644
index 0000000..88db59a
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/src/android/content/pm/cts/shortcutmanager/packages/Launcher.java
@@ -0,0 +1,21 @@
+/*
+ * 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.content.pm.cts.shortcutmanager.packages;
+
+import android.app.Activity;
+
+public class Launcher extends Activity {
+}
diff --git a/tests/tests/shortcutmanager/res/drawable-nodpi/black_1024x4096.png b/tests/tests/shortcutmanager/res/drawable-nodpi/black_1024x4096.png
new file mode 100644
index 0000000..9b2371d
--- /dev/null
+++ b/tests/tests/shortcutmanager/res/drawable-nodpi/black_1024x4096.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/res/drawable-nodpi/black_16x16.png b/tests/tests/shortcutmanager/res/drawable-nodpi/black_16x16.png
new file mode 100644
index 0000000..a26da5c
--- /dev/null
+++ b/tests/tests/shortcutmanager/res/drawable-nodpi/black_16x16.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/res/drawable-nodpi/black_16x64.png b/tests/tests/shortcutmanager/res/drawable-nodpi/black_16x64.png
new file mode 100644
index 0000000..ed049fa
--- /dev/null
+++ b/tests/tests/shortcutmanager/res/drawable-nodpi/black_16x64.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/res/drawable-nodpi/black_32x32.png b/tests/tests/shortcutmanager/res/drawable-nodpi/black_32x32.png
new file mode 100644
index 0000000..a1e25c1
--- /dev/null
+++ b/tests/tests/shortcutmanager/res/drawable-nodpi/black_32x32.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/res/drawable-nodpi/black_4096x1024.png b/tests/tests/shortcutmanager/res/drawable-nodpi/black_4096x1024.png
new file mode 100644
index 0000000..8fec244
--- /dev/null
+++ b/tests/tests/shortcutmanager/res/drawable-nodpi/black_4096x1024.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/res/drawable-nodpi/black_4096x4096.png b/tests/tests/shortcutmanager/res/drawable-nodpi/black_4096x4096.png
new file mode 100644
index 0000000..a42ff4d
--- /dev/null
+++ b/tests/tests/shortcutmanager/res/drawable-nodpi/black_4096x4096.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/res/drawable-nodpi/black_512x512.png b/tests/tests/shortcutmanager/res/drawable-nodpi/black_512x512.png
new file mode 100644
index 0000000..60304b7
--- /dev/null
+++ b/tests/tests/shortcutmanager/res/drawable-nodpi/black_512x512.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/res/drawable-nodpi/black_64x16.png b/tests/tests/shortcutmanager/res/drawable-nodpi/black_64x16.png
new file mode 100644
index 0000000..a0983c7
--- /dev/null
+++ b/tests/tests/shortcutmanager/res/drawable-nodpi/black_64x16.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/res/drawable-nodpi/black_64x64.png b/tests/tests/shortcutmanager/res/drawable-nodpi/black_64x64.png
new file mode 100644
index 0000000..7cc9373
--- /dev/null
+++ b/tests/tests/shortcutmanager/res/drawable-nodpi/black_64x64.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/res/drawable-nodpi/icon1.png b/tests/tests/shortcutmanager/res/drawable-nodpi/icon1.png
new file mode 100644
index 0000000..64eb294
--- /dev/null
+++ b/tests/tests/shortcutmanager/res/drawable-nodpi/icon1.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/res/drawable-nodpi/icon2.png b/tests/tests/shortcutmanager/res/drawable-nodpi/icon2.png
new file mode 100644
index 0000000..7502484
--- /dev/null
+++ b/tests/tests/shortcutmanager/res/drawable-nodpi/icon2.png
Binary files differ
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/MyActivity.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/MyActivity.java
new file mode 100644
index 0000000..92aec3c
--- /dev/null
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/MyActivity.java
@@ -0,0 +1,21 @@
+/*
+ * 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.content.pm.cts.shortcutmanager;
+
+import android.app.Activity;
+
+public class MyActivity extends Activity {
+}
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerClientApiTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerClientApiTest.java
new file mode 100644
index 0000000..d5b8a3e
--- /dev/null
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerClientApiTest.java
@@ -0,0 +1,1753 @@
+/*
+ * 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.content.pm.cts.shortcutmanager;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertExpectException;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.retryUntil;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.set;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.setDefaultLauncher;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import junit.framework.AssertionFailedError;
+
+/**
+ * Tests for {@link ShortcutManager} and {@link ShortcutInfo}.
+ *
+ * In this test, we tests the main functionalities of those, without throttling.  We
+ */
+@SmallTest
+public class ShortcutManagerClientApiTest extends ShortcutManagerCtsTestsBase {
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @Override
+    protected String getOverrideConfig() {
+        return "reset_interval_sec=999999,"
+                + "max_updates_per_interval=999999,"
+                + "max_shortcuts=10"
+                + "max_icon_dimension_dp=96,"
+                + "max_icon_dimension_dp_lowram=96,"
+                + "icon_format=PNG,"
+                + "icon_quality=100";
+    }
+
+    public void testShortcutInfoMissingMandatoryFields() {
+
+        final ComponentName mainActivity = new ComponentName(
+                getTestContext().getPackageName(), "android.content.pm.cts.shortcutmanager.main");
+
+        assertExpectException(
+                RuntimeException.class,
+                "id cannot be empty",
+                () -> new ShortcutInfo.Builder(getTestContext(), null));
+
+        assertExpectException(
+                RuntimeException.class,
+                "id cannot be empty",
+                () -> new ShortcutInfo.Builder(getTestContext(), ""));
+
+        assertExpectException(
+                RuntimeException.class,
+                "intents cannot contain null",
+                () -> new ShortcutInfo.Builder(getTestContext(), "id").setIntent(null));
+
+        assertExpectException(
+                RuntimeException.class,
+                "action must be set",
+                () -> new ShortcutInfo.Builder(getTestContext(), "id").setIntent(new Intent()));
+
+        assertExpectException(
+                RuntimeException.class,
+                "activity cannot be null",
+                () -> new ShortcutInfo.Builder(getTestContext(), "id").setActivity(null));
+
+        assertExpectException(
+                RuntimeException.class,
+                "shortLabel cannot be empty",
+                () -> new ShortcutInfo.Builder(getTestContext(), "id").setShortLabel(null));
+
+        assertExpectException(
+                RuntimeException.class,
+                "shortLabel cannot be empty",
+                () -> new ShortcutInfo.Builder(getTestContext(), "id").setShortLabel(""));
+
+        assertExpectException(
+                RuntimeException.class,
+                "longLabel cannot be empty",
+                () -> new ShortcutInfo.Builder(getTestContext(), "id").setLongLabel(null));
+
+        assertExpectException(
+                RuntimeException.class,
+                "longLabel cannot be empty",
+                () -> new ShortcutInfo.Builder(getTestContext(), "id").setLongLabel(""));
+
+        assertExpectException(
+                RuntimeException.class,
+                "disabledMessage cannot be empty",
+                () -> new ShortcutInfo.Builder(getTestContext(), "id").setDisabledMessage(null));
+
+        assertExpectException(
+                RuntimeException.class,
+                "disabledMessage cannot be empty",
+                () -> new ShortcutInfo.Builder(getTestContext(), "id").setDisabledMessage(""));
+
+        assertExpectException(NullPointerException.class, "action must be set",
+                () -> new ShortcutInfo.Builder(getTestContext(), "id").setIntent(new Intent()));
+
+        assertExpectException(
+                IllegalArgumentException.class, "Short label must be provided", () -> {
+                    ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
+                            .build();
+                    assertTrue(getManager().setDynamicShortcuts(list(si)));
+                });
+
+        // same for add.
+        assertExpectException(
+                IllegalArgumentException.class, "Short label must be provided", () -> {
+                    ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
+                            .setActivity(mainActivity)
+                            .build();
+                    assertTrue(getManager().addDynamicShortcuts(list(si)));
+                });
+
+        assertExpectException(NullPointerException.class, "Intent must be provided", () -> {
+            ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
+                    .setActivity(mainActivity)
+                    .setShortLabel("x")
+                    .build();
+            assertTrue(getManager().setDynamicShortcuts(list(si)));
+        });
+
+        // same for add.
+        assertExpectException(NullPointerException.class, "Intent must be provided", () -> {
+            ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
+                    .setActivity(mainActivity)
+                    .setShortLabel("x")
+                    .build();
+            assertTrue(getManager().addDynamicShortcuts(list(si)));
+        });
+
+        assertExpectException(
+                IllegalStateException.class, "does not belong to package", () -> {
+                    ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
+                            .setActivity(new ComponentName("xxx", "s"))
+                            .build();
+                    assertTrue(getManager().setDynamicShortcuts(list(si)));
+                });
+
+        // same for add.
+        assertExpectException(
+                IllegalStateException.class, "does not belong to package", () -> {
+                    ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
+                            .setActivity(new ComponentName("xxx", "s"))
+                            .build();
+                    assertTrue(getManager().addDynamicShortcuts(list(si)));
+                });
+
+        // Not main activity
+        final ComponentName nonMainActivity = new ComponentName(
+                getTestContext().getPackageName(),
+                "android.content.pm.cts.shortcutmanager.non_main");
+        assertExpectException(
+                IllegalStateException.class, "is not main", () -> {
+                    ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
+                            .setActivity(nonMainActivity)
+                            .build();
+                    assertTrue(getManager().setDynamicShortcuts(list(si)));
+                });
+        // For add
+        assertExpectException(
+                IllegalStateException.class, "is not main", () -> {
+                    ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
+                            .setActivity(nonMainActivity)
+                            .build();
+                    assertTrue(getManager().addDynamicShortcuts(list(si)));
+                });
+        // For update
+        assertExpectException(
+                IllegalStateException.class, "is not main", () -> {
+                    ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
+                            .setActivity(nonMainActivity)
+                            .build();
+                    assertTrue(getManager().updateShortcuts(list(si)));
+                });
+
+        // Main activity, but disabled.
+        final ComponentName disabledMain = new ComponentName(
+                getTestContext().getPackageName(),
+                "android.content.pm.cts.shortcutmanager.disabled_main");
+        assertExpectException(
+                IllegalStateException.class, "is not main", () -> {
+                    ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
+                            .setActivity(disabledMain)
+                            .build();
+                    assertTrue(getManager().setDynamicShortcuts(list(si)));
+                });
+        // For add
+        assertExpectException(
+                IllegalStateException.class, "is not main", () -> {
+                    ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
+                            .setActivity(disabledMain)
+                            .build();
+                    assertTrue(getManager().addDynamicShortcuts(list(si)));
+                });
+        // For update
+        assertExpectException(
+                IllegalStateException.class, "is not main", () -> {
+                    ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
+                            .setActivity(disabledMain)
+                            .build();
+                    assertTrue(getManager().updateShortcuts(list(si)));
+                });
+    }
+
+    public void testSetDynamicShortcuts() {
+        runWithCaller(mPackageContext1, () -> {
+            assertTrue(getManager().setDynamicShortcuts(list(
+                    makeShortcut("s1", "title1"),
+                    makeShortcut("s2", "title2"),
+                    makeShortcut("s3", "title3"))));
+        });
+
+        runWithCaller(mPackageContext1, () -> {
+            assertWith(getManager().getDynamicShortcuts())
+                    .areAllEnabled()
+                    .areAllDynamic()
+                    .haveIds("s1", "s2", "s3")
+                    .forShortcutWithId("s1", si -> {
+                        assertEquals("title1", si.getShortLabel());
+                    })
+                    .forShortcutWithId("s2", si -> {
+                        assertEquals("title2", si.getShortLabel());
+                    })
+                    .forShortcutWithId("s3", si -> {
+                        assertEquals("title3", si.getShortLabel());
+                    });
+            assertWith(getManager().getPinnedShortcuts())
+                    .isEmpty();
+            assertWith(getManager().getManifestShortcuts())
+                    .isEmpty();
+        });
+
+        // Publish from different package.
+        runWithCaller(mPackageContext2, () -> {
+            assertTrue(getManager().setDynamicShortcuts(list(
+                    makeShortcut("s1x", "title1x"))));
+        });
+
+        runWithCaller(mPackageContext2, () -> {
+            assertWith(getManager().getDynamicShortcuts())
+                    .areAllEnabled()
+                    .areAllDynamic()
+                    .haveIds("s1x")
+                    .forShortcutWithId("s1x", si -> {
+                        assertEquals("title1x", si.getShortLabel());
+                    });
+            assertWith(getManager().getPinnedShortcuts())
+                    .isEmpty();
+            assertWith(getManager().getManifestShortcuts())
+                    .isEmpty();
+        });
+
+        // Package 1 still has the same shortcuts.
+        runWithCaller(mPackageContext1, () -> {
+            assertWith(getManager().getDynamicShortcuts())
+                    .areAllEnabled()
+                    .areAllDynamic()
+                    .haveIds("s1", "s2", "s3")
+                    .forShortcutWithId("s1", si -> {
+                        assertEquals("title1", si.getShortLabel());
+                    })
+                    .forShortcutWithId("s2", si -> {
+                        assertEquals("title2", si.getShortLabel());
+                    })
+                    .forShortcutWithId("s3", si -> {
+                        assertEquals("title3", si.getShortLabel());
+                    });
+            assertWith(getManager().getPinnedShortcuts())
+                    .isEmpty();
+            assertWith(getManager().getManifestShortcuts())
+                    .isEmpty();
+        });
+
+        runWithCaller(mPackageContext1, () -> {
+            assertTrue(getManager().setDynamicShortcuts(list(
+                    makeShortcut("s2", "title2-updated"))));
+        });
+
+        runWithCaller(mPackageContext1, () -> {
+            assertWith(getManager().getDynamicShortcuts())
+                    .areAllEnabled()
+                    .areAllDynamic()
+                    .haveIds("s2")
+                    .forShortcutWithId("s2", si -> {
+                        assertEquals("title2-updated", si.getShortLabel());
+                    });
+            assertWith(getManager().getPinnedShortcuts())
+                    .isEmpty();
+            assertWith(getManager().getManifestShortcuts())
+                    .isEmpty();
+        });
+
+        runWithCaller(mPackageContext1, () -> {
+            assertTrue(getManager().setDynamicShortcuts(list()));
+        });
+
+        runWithCaller(mPackageContext1, () -> {
+            assertWith(getManager().getDynamicShortcuts())
+                    .isEmpty();
+            assertWith(getManager().getPinnedShortcuts())
+                    .isEmpty();
+            assertWith(getManager().getManifestShortcuts())
+                    .isEmpty();
+        });
+
+        // Package2 still has the same shortcuts.
+        runWithCaller(mPackageContext2, () -> {
+            assertWith(getManager().getDynamicShortcuts())
+                    .areAllEnabled()
+                    .areAllDynamic()
+                    .haveIds("s1x")
+                    .forShortcutWithId("s1x", si -> {
+                        assertEquals("title1x", si.getShortLabel());
+                    });
+            assertWith(getManager().getPinnedShortcuts())
+                    .isEmpty();
+            assertWith(getManager().getManifestShortcuts())
+                    .isEmpty();
+        });
+    }
+
+    public void testSetDynamicShortcuts_details() throws Exception {
+        final Icon icon1 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+                getTestContext().getResources(), R.drawable.black_16x64));
+        final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+                getTestContext().getResources(), R.drawable.black_32x32));
+        final Icon icon3 = loadPackageDrawableIcon(mPackageContext1, "black_64x16");
+        final Icon icon4 = loadPackageDrawableIcon(mPackageContext1, "black_64x64");
+
+        runWithCaller(mPackageContext1, () -> {
+            final ShortcutInfo source = makeShortcutBuilder("s1")
+                    .setShortLabel("shortlabel")
+                    .setLongLabel("longlabel")
+                    .setIcon(icon1)
+                    .setActivity(getActivity("Launcher"))
+                    .setDisabledMessage("disabledmessage")
+                    .setIntents(new Intent[]{new Intent("view").putExtra("k1", "v1")})
+                    .setExtras(makePersistableBundle("ek1", "ev1"))
+                    .setCategories(set("cat1"))
+                    .build();
+
+            assertTrue(getManager().setDynamicShortcuts(list(source)));
+
+            // Check each field.
+            assertWith(getManager().getDynamicShortcuts())
+                    .forShortcutWithId("s1", si ->{
+                        assertEquals("shortlabel", si.getShortLabel());
+                        assertEquals("longlabel", si.getLongLabel());
+                        assertEquals(getActivity("Launcher"), si.getActivity());
+                        assertEquals("disabledmessage", si.getDisabledMessage());
+                        assertEquals(1, si.getIntents().length);
+                        assertEquals("view", si.getIntents()[0].getAction());
+                        assertEquals("v1", si.getIntents()[0].getStringExtra("k1"));
+                        assertEquals("ev1", si.getExtras().getString("ek1"));
+                        assertEquals(set("cat1"), si.getCategories());
+                    });
+            assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+                    icon1);
+        });
+
+        runWithCaller(mPackageContext1, () -> {
+            // No fields updated.
+            final ShortcutInfo updated = makeShortcutBuilder("s1")
+                    .setShortLabel("xxx")
+                    .setIntents(new Intent[]{new Intent("main").putExtra("k1", "yyy")})
+                    .build();
+
+            assertTrue(getManager().setDynamicShortcuts(list(updated)));
+
+            // Check each field.
+            assertWith(getManager().getDynamicShortcuts())
+                    .forShortcutWithId("s1", si ->{
+                        assertEquals("xxx", si.getShortLabel());
+                        assertEquals(null, si.getLongLabel());
+                        assertEquals(getActivity("Launcher"), si.getActivity());
+                        assertEquals(null, si.getDisabledMessage());
+                        assertEquals(1, si.getIntents().length);
+                        assertEquals("main", si.getIntents()[0].getAction());
+                        assertEquals("yyy", si.getIntents()[0].getStringExtra("k1"));
+                        assertEquals(null, si.getExtras());
+                        assertEquals(null, si.getCategories());
+                    });
+            assertNull(
+                    getIconAsLauncher(mLauncherContext1, mPackageContext1.getPackageName(), "s1"));
+        });
+
+        runWithCaller(mPackageContext1, () -> {
+            final ShortcutInfo source = makeShortcutBuilder("s1")
+                    .setShortLabel("shortlabel")
+                    .setLongLabel("longlabel")
+                    .setIcon(icon1)
+                    .setActivity(getActivity("Launcher"))
+                    .setDisabledMessage("disabledmessage")
+                    .setIntents(new Intent[]{new Intent("view").putExtra("k1", "v1")})
+                    .setExtras(makePersistableBundle("ek1", "ev1"))
+                    .setCategories(set("cat1"))
+                    .build();
+
+            assertTrue(getManager().setDynamicShortcuts(list(source)));
+
+            // Check each field.
+            assertWith(getManager().getDynamicShortcuts())
+                    .forShortcutWithId("s1", si ->{
+                        assertEquals("shortlabel", si.getShortLabel());
+                        assertEquals("longlabel", si.getLongLabel());
+                        assertEquals(getActivity("Launcher"), si.getActivity());
+                        assertEquals("disabledmessage", si.getDisabledMessage());
+                        assertEquals(1, si.getIntents().length);
+                        assertEquals("view", si.getIntents()[0].getAction());
+                        assertEquals("v1", si.getIntents()[0].getStringExtra("k1"));
+                        assertEquals("ev1", si.getExtras().getString("ek1"));
+                        assertEquals(set("cat1"), si.getCategories());
+                    });
+            assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+                    icon1);
+        });
+
+        // paranoid icon check
+        runWithCaller(mPackageContext1, () -> {
+            // No fields updated.
+            final ShortcutInfo updated = makeShortcutBuilder("s1")
+                    .setShortLabel("xxx")
+                    .setIntents(new Intent[]{new Intent("main").putExtra("k1", "yyy")})
+                    .setIcon(icon2)
+                    .build();
+
+            assertTrue(getManager().setDynamicShortcuts(list(updated)));
+
+            // Check each field.
+            assertWith(getManager().getDynamicShortcuts())
+                    .forShortcutWithId("s1", si ->{
+                        assertEquals("xxx", si.getShortLabel());
+                    });
+            assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+                    icon2);
+        });
+
+        runWithCaller(mPackageContext1, () -> {
+            // No fields updated.
+            final ShortcutInfo updated = makeShortcutBuilder("s1")
+                    .setShortLabel("xxx")
+                    .setIntents(new Intent[]{new Intent("main").putExtra("k1", "yyy")})
+                    .setIcon(icon3)
+                    .build();
+
+            assertTrue(getManager().setDynamicShortcuts(list(updated)));
+
+            // Check each field.
+            assertWith(getManager().getDynamicShortcuts())
+                    .forShortcutWithId("s1", si ->{
+                        assertEquals("xxx", si.getShortLabel());
+                    });
+            assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+                    icon3);
+        });
+
+
+        runWithCaller(mPackageContext1, () -> {
+            // No fields updated.
+            final ShortcutInfo updated = makeShortcutBuilder("s1")
+                    .setShortLabel("xxx")
+                    .setIntents(new Intent[]{new Intent("main").putExtra("k1", "yyy")})
+                    .setIcon(icon4)
+                    .build();
+
+            assertTrue(getManager().setDynamicShortcuts(list(updated)));
+
+            // Check each field.
+            assertWith(getManager().getDynamicShortcuts())
+                    .forShortcutWithId("s1", si ->{
+                        assertEquals("xxx", si.getShortLabel());
+                    });
+            assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+                    icon4);
+        });
+    }
+
+    public void testSetDynamicShortcuts_wasPinned() throws Exception {
+        // Create s1 as a floating pinned shortcut.
+        runWithCaller(mPackageContext1, () -> {
+            assertTrue(getManager().setDynamicShortcuts(list(
+                    makeShortcut("s1"))));
+        });
+
+        setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+        runWithCaller(mLauncherContext1, () -> {
+            getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
+                    list("s1"), getUserHandle());
+        });
+        runWithCaller(mPackageContext1, () -> {
+            getManager().removeDynamicShortcuts(list("s1"));
+
+            assertWith(getManager().getDynamicShortcuts())
+                    .isEmpty();
+            assertWith(getManager().getPinnedShortcuts())
+                    .haveIds("s1");
+        });
+
+        // Then run the same test.
+        testSetDynamicShortcuts_details();
+    }
+
+    public void testAddDynamicShortcuts() {
+        runWithCaller(mPackageContext1, () -> {
+            assertTrue(getManager().addDynamicShortcuts(list(
+                    makeShortcut("s1", "title1"),
+                    makeShortcut("s2", "title2"),
+                    makeShortcut("s3", "title3"))));
+        });
+
+        runWithCaller(mPackageContext1, () -> {
+            assertWith(getManager().getDynamicShortcuts())
+                    .areAllEnabled()
+                    .areAllDynamic()
+                    .haveIds("s1", "s2", "s3")
+                    .forShortcutWithId("s1", si -> {
+                        assertEquals("title1", si.getShortLabel());
+                    })
+                    .forShortcutWithId("s2", si -> {
+                        assertEquals("title2", si.getShortLabel());
+                    })
+                    .forShortcutWithId("s3", si -> {
+                        assertEquals("title3", si.getShortLabel());
+                    });
+            assertWith(getManager().getPinnedShortcuts())
+                    .isEmpty();
+            assertWith(getManager().getManifestShortcuts())
+                    .isEmpty();
+        });
+
+        // Publish from different package.
+        runWithCaller(mPackageContext2, () -> {
+            assertTrue(getManager().addDynamicShortcuts(list(
+                    makeShortcut("s1x", "title1x"))));
+        });
+
+        runWithCaller(mPackageContext2, () -> {
+            assertWith(getManager().getDynamicShortcuts())
+                    .areAllEnabled()
+                    .areAllDynamic()
+                    .haveIds("s1x")
+                    .forShortcutWithId("s1x", si -> {
+                        assertEquals("title1x", si.getShortLabel());
+                    });
+            assertWith(getManager().getPinnedShortcuts())
+                    .isEmpty();
+            assertWith(getManager().getManifestShortcuts())
+                    .isEmpty();
+        });
+
+        // Package 1 still has the same shortcuts.
+        runWithCaller(mPackageContext1, () -> {
+            assertWith(getManager().getDynamicShortcuts())
+                    .areAllEnabled()
+                    .areAllDynamic()
+                    .haveIds("s1", "s2", "s3")
+                    .forShortcutWithId("s1", si -> {
+                        assertEquals("title1", si.getShortLabel());
+                    })
+                    .forShortcutWithId("s2", si -> {
+                        assertEquals("title2", si.getShortLabel());
+                    })
+                    .forShortcutWithId("s3", si -> {
+                        assertEquals("title3", si.getShortLabel());
+                    });
+            assertWith(getManager().getPinnedShortcuts())
+                    .isEmpty();
+            assertWith(getManager().getManifestShortcuts())
+                    .isEmpty();
+        });
+
+        runWithCaller(mPackageContext1, () -> {
+            assertTrue(getManager().addDynamicShortcuts(list(
+                    makeShortcut("s2", "title2-updated"))));
+        });
+
+        runWithCaller(mPackageContext1, () -> {
+            assertWith(getManager().getDynamicShortcuts())
+                    .areAllEnabled()
+                    .areAllDynamic()
+                    .haveIds("s1", "s2", "s3")
+                    .forShortcutWithId("s1", si -> {
+                        assertEquals("title1", si.getShortLabel());
+                    })
+                    .forShortcutWithId("s2", si -> {
+                        assertEquals("title2-updated", si.getShortLabel());
+                    })
+                    .forShortcutWithId("s3", si -> {
+                        assertEquals("title3", si.getShortLabel());
+                    });
+            assertWith(getManager().getPinnedShortcuts())
+                    .isEmpty();
+            assertWith(getManager().getManifestShortcuts())
+                    .isEmpty();
+        });
+
+        runWithCaller(mPackageContext1, () -> {
+            assertTrue(getManager().addDynamicShortcuts(list()));
+        });
+
+        runWithCaller(mPackageContext1, () -> {
+            assertWith(getManager().getDynamicShortcuts())
+                    .areAllEnabled()
+                    .areAllDynamic()
+                    .haveIds("s1", "s2", "s3")
+                    .forShortcutWithId("s1", si -> {
+                        assertEquals("title1", si.getShortLabel());
+                    })
+                    .forShortcutWithId("s2", si -> {
+                        assertEquals("title2-updated", si.getShortLabel());
+                    })
+                    .forShortcutWithId("s3", si -> {
+                        assertEquals("title3", si.getShortLabel());
+                    });
+            assertWith(getManager().getPinnedShortcuts())
+                    .isEmpty();
+            assertWith(getManager().getManifestShortcuts())
+                    .isEmpty();
+        });
+
+        // Package2 still has the same shortcuts.
+        runWithCaller(mPackageContext2, () -> {
+            assertWith(getManager().getDynamicShortcuts())
+                    .areAllEnabled()
+                    .areAllDynamic()
+                    .haveIds("s1x")
+                    .forShortcutWithId("s1x", si -> {
+                        assertEquals("title1x", si.getShortLabel());
+                    });
+            assertWith(getManager().getPinnedShortcuts())
+                    .isEmpty();
+            assertWith(getManager().getManifestShortcuts())
+                    .isEmpty();
+        });
+    }
+
+    public void testAddDynamicShortcuts_details() throws Exception {
+        final Icon icon1 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+                getTestContext().getResources(), R.drawable.black_16x64));
+        final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+                getTestContext().getResources(), R.drawable.black_32x32));
+        final Icon icon3 = loadPackageDrawableIcon(mPackageContext1, "black_64x16");
+        final Icon icon4 = loadPackageDrawableIcon(mPackageContext1, "black_64x64");
+
+        runWithCaller(mPackageContext1, () -> {
+            final ShortcutInfo source = makeShortcutBuilder("s1")
+                    .setShortLabel("shortlabel")
+                    .setLongLabel("longlabel")
+                    .setIcon(icon1)
+                    .setActivity(getActivity("Launcher"))
+                    .setDisabledMessage("disabledmessage")
+                    .setIntents(new Intent[]{new Intent("view").putExtra("k1", "v1")})
+                    .setExtras(makePersistableBundle("ek1", "ev1"))
+                    .setCategories(set("cat1"))
+                    .build();
+
+            assertTrue(getManager().addDynamicShortcuts(list(source)));
+
+            // Check each field.
+            assertWith(getManager().getDynamicShortcuts())
+                    .forShortcutWithId("s1", si ->{
+                        assertEquals("shortlabel", si.getShortLabel());
+                        assertEquals("longlabel", si.getLongLabel());
+                        assertEquals(getActivity("Launcher"), si.getActivity());
+                        assertEquals("disabledmessage", si.getDisabledMessage());
+                        assertEquals(1, si.getIntents().length);
+                        assertEquals("view", si.getIntents()[0].getAction());
+                        assertEquals("v1", si.getIntents()[0].getStringExtra("k1"));
+                        assertEquals("ev1", si.getExtras().getString("ek1"));
+                        assertEquals(set("cat1"), si.getCategories());
+                    });
+            assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+                    icon1);
+        });
+
+        runWithCaller(mPackageContext1, () -> {
+            // No fields updated.
+            final ShortcutInfo updated = makeShortcutBuilder("s1")
+                    .setShortLabel("xxx")
+                    .setIntents(new Intent[]{new Intent("main").putExtra("k1", "yyy")})
+                    .build();
+
+            assertTrue(getManager().addDynamicShortcuts(list(updated)));
+
+            // Check each field.
+            assertWith(getManager().getDynamicShortcuts())
+                    .forShortcutWithId("s1", si ->{
+                        assertEquals("xxx", si.getShortLabel());
+                        assertEquals(null, si.getLongLabel());
+                        assertEquals(getActivity("Launcher"), si.getActivity());
+                        assertEquals(null, si.getDisabledMessage());
+                        assertEquals(1, si.getIntents().length);
+                        assertEquals("main", si.getIntents()[0].getAction());
+                        assertEquals("yyy", si.getIntents()[0].getStringExtra("k1"));
+                        assertEquals(null, si.getExtras());
+                        assertEquals(null, si.getCategories());
+                    });
+            assertNull(
+                    getIconAsLauncher(mLauncherContext1, mPackageContext1.getPackageName(), "s1"));
+        });
+
+        runWithCaller(mPackageContext1, () -> {
+            final ShortcutInfo source = makeShortcutBuilder("s1")
+                    .setShortLabel("shortlabel")
+                    .setLongLabel("longlabel")
+                    .setIcon(icon1)
+                    .setActivity(getActivity("Launcher"))
+                    .setDisabledMessage("disabledmessage")
+                    .setIntents(new Intent[]{new Intent("view").putExtra("k1", "v1")})
+                    .setExtras(makePersistableBundle("ek1", "ev1"))
+                    .setCategories(set("cat1"))
+                    .build();
+
+            assertTrue(getManager().addDynamicShortcuts(list(source)));
+
+            // Check each field.
+            assertWith(getManager().getDynamicShortcuts())
+                    .forShortcutWithId("s1", si ->{
+                        assertEquals("shortlabel", si.getShortLabel());
+                        assertEquals("longlabel", si.getLongLabel());
+                        assertEquals(getActivity("Launcher"), si.getActivity());
+                        assertEquals("disabledmessage", si.getDisabledMessage());
+                        assertEquals(1, si.getIntents().length);
+                        assertEquals("view", si.getIntents()[0].getAction());
+                        assertEquals("v1", si.getIntents()[0].getStringExtra("k1"));
+                        assertEquals("ev1", si.getExtras().getString("ek1"));
+                        assertEquals(set("cat1"), si.getCategories());
+                    });
+            assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+                    icon1);
+        });
+
+        // paranoid icon check
+        runWithCaller(mPackageContext1, () -> {
+            // No fields updated.
+            final ShortcutInfo updated = makeShortcutBuilder("s1")
+                    .setShortLabel("xxx")
+                    .setIntents(new Intent[]{new Intent("main").putExtra("k1", "yyy")})
+                    .setIcon(icon2)
+                    .build();
+
+            assertTrue(getManager().addDynamicShortcuts(list(updated)));
+
+            // Check each field.
+            assertWith(getManager().getDynamicShortcuts())
+                    .forShortcutWithId("s1", si ->{
+                        assertEquals("xxx", si.getShortLabel());
+                    });
+            assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+                    icon2);
+        });
+
+        runWithCaller(mPackageContext1, () -> {
+            // No fields updated.
+            final ShortcutInfo updated = makeShortcutBuilder("s1")
+                    .setShortLabel("xxx")
+                    .setIntents(new Intent[]{new Intent("main").putExtra("k1", "yyy")})
+                    .setIcon(icon3)
+                    .build();
+
+            assertTrue(getManager().addDynamicShortcuts(list(updated)));
+
+            // Check each field.
+            assertWith(getManager().getDynamicShortcuts())
+                    .forShortcutWithId("s1", si ->{
+                        assertEquals("xxx", si.getShortLabel());
+                    });
+            assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+                    icon3);
+        });
+
+
+        runWithCaller(mPackageContext1, () -> {
+            // No fields updated.
+            final ShortcutInfo updated = makeShortcutBuilder("s1")
+                    .setShortLabel("xxx")
+                    .setIntents(new Intent[]{new Intent("main").putExtra("k1", "yyy")})
+                    .setIcon(icon4)
+                    .build();
+
+            assertTrue(getManager().addDynamicShortcuts(list(updated)));
+
+            // Check each field.
+            assertWith(getManager().getDynamicShortcuts())
+                    .forShortcutWithId("s1", si ->{
+                        assertEquals("xxx", si.getShortLabel());
+                    });
+            assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+                    icon4);
+        });
+    }
+
+    public void testAddDynamicShortcuts_wasPinned() throws Exception {
+        // Create s1 as a floating pinned shortcut.
+        runWithCaller(mPackageContext1, () -> {
+            assertTrue(getManager().setDynamicShortcuts(list(
+                    makeShortcut("s1"))));
+        });
+
+        setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+        runWithCaller(mLauncherContext1, () -> {
+            getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
+                    list("s1"), getUserHandle());
+        });
+        runWithCaller(mPackageContext1, () -> {
+            getManager().removeDynamicShortcuts(list("s1"));
+
+            assertWith(getManager().getDynamicShortcuts())
+                    .isEmpty();
+            assertWith(getManager().getPinnedShortcuts())
+                    .haveIds("s1");
+        });
+
+        // Then run the same test.
+        testAddDynamicShortcuts_details();
+    }
+
+    public void testUpdateShortcut() {
+        runWithCaller(mPackageContext1, () -> {
+            assertTrue(getManager().setDynamicShortcuts(list(
+                    makeShortcut("s1", "1a"),
+                    makeShortcut("s2", "2a"),
+                    makeShortcut("s3", "3a"))));
+        });
+        runWithCaller(mPackageContext2, () -> {
+            assertTrue(getManager().setDynamicShortcuts(list(
+                    makeShortcut("s1", "1b"),
+                    makeShortcut("s2", "2b"),
+                    makeShortcut("s3", "3b"))));
+        });
+
+        setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+        runWithCaller(mLauncherContext1, () -> {
+            getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
+                    list("s2", "s3"), getUserHandle());
+            getLauncherApps().pinShortcuts(mPackageContext2.getPackageName(),
+                    list("s1", "s2"), getUserHandle());
+        });
+        runWithCaller(mPackageContext1, () -> {
+            getManager().removeDynamicShortcuts(list("s3"));
+        });
+        runWithCaller(mPackageContext2, () -> {
+            getManager().removeDynamicShortcuts(list("s1"));
+        });
+
+        // Check the current status.
+        runWithCaller(mPackageContext1, () -> {
+            assertWith(getManager().getDynamicShortcuts())
+                    .areAllEnabled()
+                    .areAllDynamic()
+                    .haveIds("s1", "s2")
+                    .forShortcutWithId("s1", si -> {
+                        assertEquals("1a", si.getShortLabel());
+                    })
+                    .forShortcutWithId("s2", si -> {
+                        assertEquals("2a", si.getShortLabel());
+                    })
+                    ;
+            assertWith(getManager().getPinnedShortcuts())
+                    .areAllEnabled()
+                    .areAllPinned()
+                    .haveIds("s2", "s3")
+                    .forShortcutWithId("s2", si -> {
+                        assertEquals("2a", si.getShortLabel());
+                    })
+                    .forShortcutWithId("s3", si -> {
+                        assertEquals("3a", si.getShortLabel());
+                    })
+                    ;
+            assertWith(getManager().getManifestShortcuts())
+                    .isEmpty();
+        });
+        runWithCaller(mPackageContext2, () -> {
+            assertWith(getManager().getDynamicShortcuts())
+                    .areAllEnabled()
+                    .areAllDynamic()
+                    .haveIds("s2", "s3")
+                    .forShortcutWithId("s2", si -> {
+                        assertEquals("2b", si.getShortLabel());
+                    })
+                    .forShortcutWithId("s3", si -> {
+                        assertEquals("3b", si.getShortLabel());
+                    })
+                    ;
+            assertWith(getManager().getPinnedShortcuts())
+                    .areAllEnabled()
+                    .areAllPinned()
+                    .haveIds("s1", "s2")
+                    .forShortcutWithId("s1", si -> {
+                        assertEquals("1b", si.getShortLabel());
+                    })
+                    .forShortcutWithId("s2", si -> {
+                        assertEquals("2b", si.getShortLabel());
+                    })
+                    ;
+            assertWith(getManager().getManifestShortcuts())
+                    .isEmpty();
+        });
+
+        // finally, call update.
+        runWithCaller(mPackageContext1, () -> {
+            assertTrue(getManager().updateShortcuts(list(
+                    makeShortcut("s1", "upd1a"),
+                    makeShortcut("s2", "upd2a"),
+                    makeShortcut("xxx") // doen't exist -> ignored.
+                    )));
+        });
+        runWithCaller(mPackageContext2, () -> {
+            assertTrue(getManager().updateShortcuts(list(
+                    makeShortcut("s1", "upd1b"),
+                    makeShortcut("s2", "upd2b"))));
+        });
+
+        // check.
+        runWithCaller(mPackageContext1, () -> {
+            assertWith(getManager().getDynamicShortcuts())
+                    .areAllEnabled()
+                    .areAllDynamic()
+                    .haveIds("s1", "s2")
+                    .forShortcutWithId("s1", si -> {
+                        assertEquals("upd1a", si.getShortLabel());
+                    })
+                    .forShortcutWithId("s2", si -> {
+                        assertEquals("upd2a", si.getShortLabel());
+                    })
+            ;
+            assertWith(getManager().getPinnedShortcuts())
+                    .areAllEnabled()
+                    .areAllPinned()
+                    .haveIds("s2", "s3")
+                    .forShortcutWithId("s2", si -> {
+                        assertEquals("upd2a", si.getShortLabel());
+                    })
+                    .forShortcutWithId("s3", si -> {
+                        assertEquals("3a", si.getShortLabel());
+                    })
+            ;
+            assertWith(getManager().getManifestShortcuts())
+                    .isEmpty();
+        });
+        runWithCaller(mPackageContext2, () -> {
+            assertWith(getManager().getDynamicShortcuts())
+                    .areAllEnabled()
+                    .areAllDynamic()
+                    .haveIds("s2", "s3")
+                    .forShortcutWithId("s2", si -> {
+                        assertEquals("upd2b", si.getShortLabel());
+                    })
+                    .forShortcutWithId("s3", si -> {
+                        assertEquals("3b", si.getShortLabel());
+                    })
+            ;
+            assertWith(getManager().getPinnedShortcuts())
+                    .areAllEnabled()
+                    .areAllPinned()
+                    .haveIds("s1", "s2")
+                    .forShortcutWithId("s1", si -> {
+                        assertEquals("upd1b", si.getShortLabel());
+                    })
+                    .forShortcutWithId("s2", si -> {
+                        assertEquals("upd2b", si.getShortLabel());
+                    })
+            ;
+            assertWith(getManager().getManifestShortcuts())
+                    .isEmpty();
+        });
+    }
+
+    public void testUpdateShortcut_details() throws Exception {
+        final Icon icon1 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+                getTestContext().getResources(), R.drawable.black_16x64));
+        final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+                getTestContext().getResources(), R.drawable.black_32x32));
+        final Icon icon3 = loadPackageDrawableIcon(mPackageContext1, "black_64x16");
+        final Icon icon4 = loadPackageDrawableIcon(mPackageContext1, "black_64x64");
+
+        runWithCaller(mPackageContext1, () -> {
+            final ShortcutInfo source = makeShortcutBuilder("s1")
+                    .setShortLabel("shortlabel")
+                    .setLongLabel("longlabel")
+                    .setIcon(icon1)
+                    .setActivity(getActivity("Launcher"))
+                    .setDisabledMessage("disabledmessage")
+                    .setIntents(new Intent[]{new Intent("view").putExtra("k1", "v1")})
+                    .setExtras(makePersistableBundle("ek1", "ev1"))
+                    .setCategories(set("cat1"))
+                    .build();
+
+            assertTrue(getManager().setDynamicShortcuts(list(source)));
+
+            // Check each field.
+            assertWith(getManager().getDynamicShortcuts())
+                    .forShortcutWithId("s1", si ->{
+                        assertEquals("shortlabel", si.getShortLabel());
+                        assertEquals("longlabel", si.getLongLabel());
+                        assertEquals(getActivity("Launcher"), si.getActivity());
+                        assertEquals("disabledmessage", si.getDisabledMessage());
+                        assertEquals(1, si.getIntents().length);
+                        assertEquals("view", si.getIntents()[0].getAction());
+                        assertEquals("v1", si.getIntents()[0].getStringExtra("k1"));
+                        assertEquals("ev1", si.getExtras().getString("ek1"));
+                        assertEquals(set("cat1"), si.getCategories());
+                    });
+            assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+                    icon1);
+        });
+
+        runWithCaller(mPackageContext1, () -> {
+            // No fields updated.
+            final ShortcutInfo updated = makeShortcutBuilder("s1")
+                    .build();
+
+            assertTrue(getManager().updateShortcuts(list(updated)));
+
+            // Check each field.
+            assertWith(getManager().getDynamicShortcuts())
+                    .forShortcutWithId("s1", si ->{
+                        assertEquals("shortlabel", si.getShortLabel());
+                        assertEquals("longlabel", si.getLongLabel());
+                        assertEquals(getActivity("Launcher"), si.getActivity());
+                        assertEquals("disabledmessage", si.getDisabledMessage());
+                        assertEquals(1, si.getIntents().length);
+                        assertEquals("view", si.getIntents()[0].getAction());
+                        assertEquals("v1", si.getIntents()[0].getStringExtra("k1"));
+                        assertEquals("ev1", si.getExtras().getString("ek1"));
+                        assertEquals(set("cat1"), si.getCategories());
+                    });
+            assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+                    icon1);
+        });
+
+        runWithCaller(mPackageContext1, () -> {
+            final ShortcutInfo updated = makeShortcutBuilder("s1")
+                    .setShortLabel("x")
+                    .build();
+
+            assertTrue(getManager().updateShortcuts(list(updated)));
+
+            // Check each field.
+            assertWith(getManager().getDynamicShortcuts())
+                    .forShortcutWithId("s1", si ->{
+                        assertEquals("x", si.getShortLabel());
+                        assertEquals("longlabel", si.getLongLabel());
+                        assertEquals(getActivity("Launcher"), si.getActivity());
+                        assertEquals("disabledmessage", si.getDisabledMessage());
+                        assertEquals(1, si.getIntents().length);
+                        assertEquals("view", si.getIntents()[0].getAction());
+                        assertEquals("v1", si.getIntents()[0].getStringExtra("k1"));
+                        assertEquals("ev1", si.getExtras().getString("ek1"));
+                        assertEquals(set("cat1"), si.getCategories());
+                    });
+            assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+                    icon1);
+        });
+
+        runWithCaller(mPackageContext1, () -> {
+            final ShortcutInfo updated = makeShortcutBuilder("s1")
+                    .setLongLabel("y")
+                    .build();
+
+            assertTrue(getManager().updateShortcuts(list(updated)));
+
+            // Check each field.
+            assertWith(getManager().getDynamicShortcuts())
+                    .forShortcutWithId("s1", si ->{
+                        assertEquals("x", si.getShortLabel());
+                        assertEquals("y", si.getLongLabel());
+                        assertEquals(getActivity("Launcher"), si.getActivity());
+                        assertEquals("disabledmessage", si.getDisabledMessage());
+                        assertEquals(1, si.getIntents().length);
+                        assertEquals("view", si.getIntents()[0].getAction());
+                        assertEquals("v1", si.getIntents()[0].getStringExtra("k1"));
+                        assertEquals("ev1", si.getExtras().getString("ek1"));
+                        assertEquals(set("cat1"), si.getCategories());
+                    });
+            assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+                    icon1);
+        });
+
+        runWithCaller(mPackageContext1, () -> {
+            final ShortcutInfo updated = makeShortcutBuilder("s1")
+                    .setActivity(getActivity("Launcher2"))
+                    .build();
+
+            assertTrue(getManager().updateShortcuts(list(updated)));
+
+            // Check each field.
+            assertWith(getManager().getDynamicShortcuts())
+                    .forShortcutWithId("s1", si ->{
+                        assertEquals("x", si.getShortLabel());
+                        assertEquals("y", si.getLongLabel());
+                        assertEquals(getActivity("Launcher2"), si.getActivity());
+                        assertEquals("disabledmessage", si.getDisabledMessage());
+                        assertEquals(1, si.getIntents().length);
+                        assertEquals("view", si.getIntents()[0].getAction());
+                        assertEquals("v1", si.getIntents()[0].getStringExtra("k1"));
+                        assertEquals("ev1", si.getExtras().getString("ek1"));
+                        assertEquals(set("cat1"), si.getCategories());
+                    });
+            assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+                    icon1);
+        });
+
+        runWithCaller(mPackageContext1, () -> {
+            final ShortcutInfo updated = makeShortcutBuilder("s1")
+                    .setDisabledMessage("z")
+                    .build();
+
+            assertTrue(getManager().updateShortcuts(list(updated)));
+
+            // Check each field.
+            assertWith(getManager().getDynamicShortcuts())
+                    .forShortcutWithId("s1", si ->{
+                        assertEquals("x", si.getShortLabel());
+                        assertEquals("y", si.getLongLabel());
+                        assertEquals(getActivity("Launcher2"), si.getActivity());
+                        assertEquals("z", si.getDisabledMessage());
+                        assertEquals(1, si.getIntents().length);
+                        assertEquals("view", si.getIntents()[0].getAction());
+                        assertEquals("v1", si.getIntents()[0].getStringExtra("k1"));
+                        assertEquals("ev1", si.getExtras().getString("ek1"));
+                        assertEquals(set("cat1"), si.getCategories());
+                    });
+            assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+                    icon1);
+        });
+
+        runWithCaller(mPackageContext1, () -> {
+            final ShortcutInfo updated = makeShortcutBuilder("s1")
+                    .setIntents(new Intent[]{new Intent("main")})
+                    .build();
+
+            assertTrue(getManager().updateShortcuts(list(updated)));
+
+            // Check each field.
+            assertWith(getManager().getDynamicShortcuts())
+                    .forShortcutWithId("s1", si ->{
+                        assertEquals("x", si.getShortLabel());
+                        assertEquals("y", si.getLongLabel());
+                        assertEquals(getActivity("Launcher2"), si.getActivity());
+                        assertEquals("z", si.getDisabledMessage());
+                        assertEquals(1, si.getIntents().length);
+                        assertEquals("main", si.getIntents()[0].getAction());
+                        assertEquals(null, si.getIntents()[0].getStringExtra("k1"));
+                        assertEquals("ev1", si.getExtras().getString("ek1"));
+                        assertEquals(set("cat1"), si.getCategories());
+                    });
+            assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+                    icon1);
+        });
+
+        runWithCaller(mPackageContext1, () -> {
+            final ShortcutInfo updated = makeShortcutBuilder("s1")
+                    .setExtras(makePersistableBundle("ek1", "X"))
+                    .build();
+
+            assertTrue(getManager().updateShortcuts(list(updated)));
+
+            // Check each field.
+            assertWith(getManager().getDynamicShortcuts())
+                    .forShortcutWithId("s1", si ->{
+                        assertEquals("x", si.getShortLabel());
+                        assertEquals("y", si.getLongLabel());
+                        assertEquals(getActivity("Launcher2"), si.getActivity());
+                        assertEquals("z", si.getDisabledMessage());
+                        assertEquals(1, si.getIntents().length);
+                        assertEquals("main", si.getIntents()[0].getAction());
+                        assertEquals(null, si.getIntents()[0].getStringExtra("k1"));
+                        assertEquals("X", si.getExtras().getString("ek1"));
+                        assertEquals(set("cat1"), si.getCategories());
+                    });
+            assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+                    icon1);
+        });
+
+        runWithCaller(mPackageContext1, () -> {
+            final ShortcutInfo updated = makeShortcutBuilder("s1")
+                    .setCategories(set("dog"))
+                    .build();
+
+            assertTrue(getManager().updateShortcuts(list(updated)));
+
+            // Check each field.
+            assertWith(getManager().getDynamicShortcuts())
+                    .forShortcutWithId("s1", si ->{
+                        assertEquals("x", si.getShortLabel());
+                        assertEquals("y", si.getLongLabel());
+                        assertEquals(getActivity("Launcher2"), si.getActivity());
+                        assertEquals("z", si.getDisabledMessage());
+                        assertEquals(1, si.getIntents().length);
+                        assertEquals("main", si.getIntents()[0].getAction());
+                        assertEquals(null, si.getIntents()[0].getStringExtra("k1"));
+                        assertEquals("X", si.getExtras().getString("ek1"));
+                        assertEquals(set("dog"), si.getCategories());
+                    });
+            assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+                    icon1);
+        });
+
+        runWithCaller(mPackageContext1, () -> {
+            final ShortcutInfo updated = makeShortcutBuilder("s1")
+                    .setIcon(icon2)
+                    .build();
+
+            assertTrue(getManager().updateShortcuts(list(updated)));
+
+            // Check each field.
+            assertWith(getManager().getDynamicShortcuts())
+                    .forShortcutWithId("s1", si ->{
+                        assertEquals("x", si.getShortLabel());
+                        assertEquals("y", si.getLongLabel());
+                        assertEquals(getActivity("Launcher2"), si.getActivity());
+                        assertEquals("z", si.getDisabledMessage());
+                        assertEquals(1, si.getIntents().length);
+                        assertEquals("main", si.getIntents()[0].getAction());
+                        assertEquals(null, si.getIntents()[0].getStringExtra("k1"));
+                        assertEquals("X", si.getExtras().getString("ek1"));
+                        assertEquals(set("dog"), si.getCategories());
+                    });
+            assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+                    icon2);
+        });
+
+        // More paranoid tests with icons.
+        runWithCaller(mPackageContext1, () -> {
+            final ShortcutInfo updated = makeShortcutBuilder("s1")
+                    .setIcon(icon1)
+                    .build();
+
+            assertTrue(getManager().updateShortcuts(list(updated)));
+
+            assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+                    icon1);
+        });
+
+        // More paranoid tests with icons.
+        runWithCaller(mPackageContext1, () -> {
+            final ShortcutInfo updated = makeShortcutBuilder("s1")
+                    .setIcon(icon3)
+                    .build();
+
+            assertTrue(getManager().updateShortcuts(list(updated)));
+
+            assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+                    icon3);
+        });
+
+        runWithCaller(mPackageContext1, () -> {
+            final ShortcutInfo updated = makeShortcutBuilder("s1")
+                    .setIcon(icon4)
+                    .build();
+
+            assertTrue(getManager().updateShortcuts(list(updated)));
+
+            assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+                    icon4);
+        });
+
+        runWithCaller(mPackageContext1, () -> {
+            final ShortcutInfo updated = makeShortcutBuilder("s1")
+                    .setIcon(icon1)
+                    .build();
+
+            assertTrue(getManager().updateShortcuts(list(updated)));
+
+            assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+                    icon1);
+
+            // Extra paranoid.
+            boolean success = false;
+            try {
+                assertIconDimensions(mLauncherContext1, mPackageContext1.getPackageName(), "s1",
+                        icon2);
+            } catch (AssertionFailedError expected) {
+                success = true;
+            }
+            assertTrue(success);
+        });
+    }
+
+    public void testDisableAndEnableShortcut() {
+        runWithCaller(mPackageContext1, () -> {
+            assertTrue(getManager().setDynamicShortcuts(list(
+                    makeShortcut("s1", "1a"),
+                    makeShortcut("s2", "2a"),
+                    makeShortcut("s3", "3a"))));
+        });
+        runWithCaller(mPackageContext2, () -> {
+            assertTrue(getManager().setDynamicShortcuts(list(
+                    makeShortcut("s1", "1b"),
+                    makeShortcut("s2", "2b"),
+                    makeShortcut("s3", "3b"))));
+        });
+
+        setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+        runWithCaller(mLauncherContext1, () -> {
+            getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
+                    list("s2", "s3"), getUserHandle());
+            getLauncherApps().pinShortcuts(mPackageContext2.getPackageName(),
+                    list("s1", "s2"), getUserHandle());
+        });
+        runWithCaller(mPackageContext1, () -> {
+            getManager().removeDynamicShortcuts(list("s3"));
+        });
+        runWithCaller(mPackageContext2, () -> {
+            getManager().removeDynamicShortcuts(list("s1"));
+        });
+
+        // Check the current status.
+        runWithCaller(mPackageContext1, () -> {
+            assertWith(getManager().getDynamicShortcuts())
+                    .areAllEnabled()
+                    .areAllDynamic()
+                    .haveIds("s1", "s2")
+                    .forShortcutWithId("s1", si -> {
+                        assertEquals("1a", si.getShortLabel());
+                    })
+                    .forShortcutWithId("s2", si -> {
+                        assertEquals("2a", si.getShortLabel());
+                    })
+            ;
+            assertWith(getManager().getPinnedShortcuts())
+                    .areAllEnabled()
+                    .areAllPinned()
+                    .haveIds("s2", "s3")
+                    .forShortcutWithId("s2", si -> {
+                        assertEquals("2a", si.getShortLabel());
+                    })
+                    .forShortcutWithId("s3", si -> {
+                        assertEquals("3a", si.getShortLabel());
+                    })
+            ;
+            assertWith(getManager().getManifestShortcuts())
+                    .isEmpty();
+        });
+        runWithCaller(mPackageContext2, () -> {
+            assertWith(getManager().getDynamicShortcuts())
+                    .areAllEnabled()
+                    .areAllDynamic()
+                    .haveIds("s2", "s3")
+                    .forShortcutWithId("s2", si -> {
+                        assertEquals("2b", si.getShortLabel());
+                    })
+                    .forShortcutWithId("s3", si -> {
+                        assertEquals("3b", si.getShortLabel());
+                    })
+            ;
+            assertWith(getManager().getPinnedShortcuts())
+                    .areAllEnabled()
+                    .areAllPinned()
+                    .haveIds("s1", "s2")
+                    .forShortcutWithId("s1", si -> {
+                        assertEquals("1b", si.getShortLabel());
+                    })
+                    .forShortcutWithId("s2", si -> {
+                        assertEquals("2b", si.getShortLabel());
+                    })
+            ;
+            assertWith(getManager().getManifestShortcuts())
+                    .isEmpty();
+        });
+
+        // finally, call disable.
+        runWithCaller(mPackageContext1, () -> {
+            getManager().disableShortcuts(list("s1", "s3"));
+        });
+        runWithCaller(mPackageContext2, () -> {
+            getManager().disableShortcuts(list("s1", "s2"), "custom message");
+        });
+
+        // check
+        runWithCaller(mPackageContext1, () -> {
+            assertWith(getManager().getDynamicShortcuts())
+                    .areAllEnabled()
+                    .areAllDynamic()
+                    .haveIds("s2")
+                    .forShortcutWithId("s2", si -> {
+                        assertEquals("2a", si.getShortLabel());
+                    })
+            ;
+            assertWith(getManager().getPinnedShortcuts())
+                    .areAllPinned()
+                    .haveIds("s2", "s3")
+                    .forShortcutWithId("s2", si -> {
+                        assertEquals("2a", si.getShortLabel());
+                        assertTrue(si.isEnabled()); // still enabled.
+                    })
+                    .forShortcutWithId("s3", si -> {
+                        assertEquals("3a", si.getShortLabel());
+                        assertFalse(si.isEnabled()); // disabled.
+                        assertNull(si.getDisabledMessage());
+                    })
+            ;
+            assertWith(getManager().getManifestShortcuts())
+                    .isEmpty();
+        });
+
+        runWithCaller(mPackageContext2, () -> {
+            assertWith(getManager().getDynamicShortcuts())
+                    .areAllEnabled()
+                    .areAllDynamic()
+                    .haveIds("s3")
+                    .forShortcutWithId("s3", si -> {
+                        assertEquals("3b", si.getShortLabel());
+                    })
+            ;
+            assertWith(getManager().getPinnedShortcuts())
+                    .areAllDisabled()
+                    .areAllPinned()
+                    .haveIds("s1", "s2")
+                    .forShortcutWithId("s1", si -> {
+                        assertEquals("1b", si.getShortLabel());
+                        assertEquals("custom message", si.getDisabledMessage());
+                    })
+                    .forShortcutWithId("s2", si -> {
+                        assertEquals("2b", si.getShortLabel());
+                        assertEquals("custom message", si.getDisabledMessage());
+                    })
+            ;
+            assertWith(getManager().getManifestShortcuts())
+                    .isEmpty();
+        });
+
+        // try re-enable
+        runWithCaller(mPackageContext1, () -> {
+            getManager().enableShortcuts(list("s3"));
+        });
+        runWithCaller(mPackageContext1, () -> {
+            assertWith(getManager().getDynamicShortcuts())
+                    .areAllEnabled()
+                    .areAllDynamic()
+                    .haveIds("s2")
+                    .forShortcutWithId("s2", si -> {
+                        assertEquals("2a", si.getShortLabel());
+                    })
+            ;
+            assertWith(getManager().getPinnedShortcuts())
+                    .areAllPinned()
+                    .areAllEnabled()
+                    .haveIds("s2", "s3")
+                    .forShortcutWithId("s2", si -> {
+                        assertEquals("2a", si.getShortLabel());
+                    })
+                    .forShortcutWithId("s3", si -> {
+                        assertEquals("3a", si.getShortLabel());
+                    })
+            ;
+            assertWith(getManager().getManifestShortcuts())
+                    .isEmpty();
+        });
+    }
+
+    public void testImmutableShortcuts() {
+        runWithCaller(mPackageContext1, () -> {
+            enableManifestActivity("Launcher_manifest_2", true);
+
+            retryUntil(() -> getManager().getManifestShortcuts().size() == 2,
+                    "Manifest shortcuts didn't show up");
+        });
+        setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+        runWithCaller(mLauncherContext1, () -> {
+            getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
+                    list("ms21"), getUserHandle());
+        });
+        runWithCaller(mPackageContext1, () -> {
+            enableManifestActivity("Launcher_manifest_1", true);
+            enableManifestActivity("Launcher_manifest_2", false);
+
+            retryUntil(() -> getManager().getManifestShortcuts().size() == 1,
+                    "Manifest shortcuts didn't show up");
+        });
+        setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+        runWithCaller(mPackageContext1, () -> {
+            assertWith(getManager().getDynamicShortcuts())
+                    .isEmpty();
+            assertWith(getManager().getPinnedShortcuts())
+                    .areAllPinned()
+                    .haveIds("ms21")
+                    .areAllDisabled()
+                    ;
+            assertWith(getManager().getManifestShortcuts())
+                    .areAllNotPinned()
+                    .haveIds("ms1")
+                    .areAllEnabled()
+                    ;
+        });
+
+        assertExpectException(IllegalArgumentException.class,
+                "may not be manipulated via APIs",
+                () -> getManager().setDynamicShortcuts(list(makeShortcut("ms1"))));
+        assertExpectException(IllegalArgumentException.class,
+                "may not be manipulated via APIs",
+                () -> getManager().setDynamicShortcuts(list(makeShortcut("ms21"))));
+
+        assertExpectException(IllegalArgumentException.class,
+                "may not be manipulated via APIs",
+                () -> getManager().addDynamicShortcuts(list(makeShortcut("ms1"))));
+        assertExpectException(IllegalArgumentException.class,
+                "may not be manipulated via APIs",
+                () -> getManager().addDynamicShortcuts(list(makeShortcut("ms21"))));
+
+        assertExpectException(IllegalArgumentException.class,
+                "may not be manipulated via APIs",
+                () -> getManager().updateShortcuts(list(makeShortcut("ms1"))));
+        assertExpectException(IllegalArgumentException.class,
+                "may not be manipulated via APIs",
+                () -> getManager().updateShortcuts(list(makeShortcut("ms21"))));
+    }
+
+    public void testManifestDefinition() throws Exception {
+        final Icon iconMs21 = loadPackageDrawableIcon(mPackageContext1, "black_16x16");
+
+        runWithCaller(mPackageContext1, () -> {
+            enableManifestActivity("Launcher_manifest_2", true);
+
+            retryUntil(() -> getManager().getManifestShortcuts().size() > 0,
+                    "Manifest shortcuts didn't show up");
+
+            assertWith(getManager().getManifestShortcuts())
+                    .haveIds("ms21", "ms22")
+                    .forShortcutWithId("ms21", si-> {
+
+                        assertEquals("Shortcut 1", si.getShortLabel());
+                        assertEquals("Long shortcut label1", si.getLongLabel());
+                        assertEquals(getActivity("Launcher_manifest_2"), si.getActivity());
+                        assertEquals("Shortcut 1 is disabled", si.getDisabledMessage());
+                        assertEquals(set("android.shortcut.conversation",
+                                "android.shortcut.media"), si.getCategories());
+                        assertIconDimensions(iconMs21, getIconAsLauncher(
+                                mLauncherContext1, si.getPackage(), si.getId(), true));
+
+                        // Check the intent.
+                        assertEquals(1, si.getIntents().length);
+
+                        Intent i = si.getIntents()[0];
+
+                        assertEquals("android.intent.action.VIEW", i.getAction());
+                        assertEquals(null, i.getData());
+                        assertEquals(null, i.getType());
+                        assertEquals(null, i.getComponent());
+                        assertEquals(null, i.getExtras());
+                        assertEquals(Intent.FLAG_ACTIVITY_NEW_TASK |
+                                Intent.FLAG_ACTIVITY_CLEAR_TASK |
+                                Intent.FLAG_ACTIVITY_TASK_ON_HOME,
+                                i.getFlags());
+                    })
+                    .forShortcutWithId("ms22", si-> {
+                        assertEquals("Shortcut 2", si.getShortLabel());
+                        assertEquals(null, si.getLongLabel());
+                        assertEquals(getActivity("Launcher_manifest_2"), si.getActivity());
+                        assertEquals(null, si.getDisabledMessage());
+                        assertEquals(null, si.getCategories());
+                        assertNull(getIconAsLauncher(
+                                mLauncherContext1, si.getPackage(), si.getId(), true));
+
+                        // Check the intents.
+                        assertEquals(2, si.getIntents().length);
+
+                        Intent i = si.getIntents()[0];
+
+                        assertEquals("action", i.getAction());
+                        assertEquals(null, i.getData());
+                        assertEquals(null, i.getType());
+                        assertEquals(null, i.getComponent());
+                        assertEquals(null, i.getExtras());
+                        assertEquals(null, i.getCategories());
+                        assertEquals(Intent.FLAG_ACTIVITY_NEW_TASK |
+                                        Intent.FLAG_ACTIVITY_CLEAR_TASK |
+                                        Intent.FLAG_ACTIVITY_TASK_ON_HOME,
+                                i.getFlags());
+
+                        i = si.getIntents()[1];
+
+                        assertEquals("action2", i.getAction());
+                        assertEquals("data", i.getData().toString());
+                        assertEquals("a/b", i.getType());
+                        assertEquals(new ComponentName("pkg", "pkg.class"), i.getComponent());
+                        assertEquals(set("icat1", "icat2"), i.getCategories());
+                        assertEquals("value1", i.getStringExtra("key1"));
+                        assertEquals(123, i.getIntExtra("key2", -1));
+                        assertEquals(true, i.getBooleanExtra("key3", false));
+                        assertEquals(0, i.getFlags());
+
+                    })
+                    ;
+        });
+    }
+
+    public void testDynamicIntents() {
+        runWithCaller(mPackageContext1, () -> {
+
+            final ShortcutInfo s1 = makeShortcutBuilder("s1")
+                    .setShortLabel("shortlabel")
+                    .setIntents(new Intent[]{new Intent("android.intent.action.VIEW")})
+                    .build();
+
+            final Intent i1 = new Intent("action").setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+            final Intent i2 = new Intent("action2").setFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT)
+                    .setData(Uri.parse("data"))
+                    .setComponent(new ComponentName("pkg", "pkg.class"))
+                    .addCategory("icat1")
+                    .addCategory("icat2")
+                    .putExtra("key1", "value1")
+                    .putExtra("key2", 123)
+                    .putExtra("key3", true);
+
+            final ShortcutInfo s2 = makeShortcutBuilder("s2")
+                    .setShortLabel("shortlabel")
+                    .setIntents(new Intent[]{i1, i2})
+                    .build();
+
+            assertTrue(getManager().setDynamicShortcuts(list(s1, s2)));
+
+            assertWith(getManager().getDynamicShortcuts())
+                    .haveIds("s1", "s2")
+                    .forShortcutWithId("s1", si-> {
+                        assertEquals(1, si.getIntents().length);
+
+                        Intent i = si.getIntents()[0];
+
+                        assertEquals("android.intent.action.VIEW", i.getAction());
+                        assertEquals(null, i.getData());
+                        assertEquals(null, i.getType());
+                        assertEquals(null, i.getComponent());
+                        assertEquals(null, i.getExtras());
+                        assertEquals(0, i.getFlags());
+                    })
+                    .forShortcutWithId("s2", si-> {
+                        assertEquals(2, si.getIntents().length);
+
+                        Intent i = si.getIntents()[0];
+
+                        assertEquals("action", i.getAction());
+                        assertEquals(null, i.getData());
+                        assertEquals(null, i.getType());
+                        assertEquals(null, i.getComponent());
+                        assertEquals(null, i.getExtras());
+                        assertEquals(null, i.getCategories());
+                        assertEquals(Intent.FLAG_ACTIVITY_CLEAR_TASK, i.getFlags());
+
+                        i = si.getIntents()[1];
+
+                        assertEquals("action2", i.getAction());
+                        assertEquals("data", i.getData().toString());
+                        assertEquals(new ComponentName("pkg", "pkg.class"), i.getComponent());
+                        assertEquals(set("icat1", "icat2"), i.getCategories());
+                        assertEquals("value1", i.getStringExtra("key1"));
+                        assertEquals(123, i.getIntExtra("key2", -1));
+                        assertEquals(true, i.getBooleanExtra("key3", false));
+                        assertEquals(Intent.FLAG_ACTIVITY_NEW_DOCUMENT, i.getFlags());
+                    })
+            ;
+        });
+    }
+
+    public void testManifestWithErrors() {
+        runWithCaller(mPackageContext1, () -> {
+            enableManifestActivity("Launcher_manifest_error_1", true);
+            enableManifestActivity("Launcher_manifest_error_2", true);
+            enableManifestActivity("Launcher_manifest_error_3", true);
+
+            retryUntil(() -> getManager().getManifestShortcuts().size() > 0,
+                    "Manifest shortcuts didn't show up");
+
+            // Only the last one is accepted.
+            assertWith(getManager().getManifestShortcuts())
+                    .haveIds("valid")
+                    ;
+
+
+        });
+    }
+
+    public void testManifestDisabled() {
+        runWithCaller(mPackageContext1, () -> {
+            enableManifestActivity("Launcher_manifest_4a", true);
+
+            retryUntil(() -> getManager().getManifestShortcuts().size() > 0,
+                    "Manifest shortcuts didn't show up");
+
+            // First they're all enabled.
+            assertWith(getManager().getManifestShortcuts())
+                    .haveIds("ms41", "ms42", "ms43")
+                    .areAllEnabled()
+                    ;
+        });
+        setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+        runWithCaller(mLauncherContext1, () -> {
+            getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
+                    list("ms41", "ms42"), getUserHandle());
+        });
+        runWithCaller(mPackageContext1, () -> {
+            enableManifestActivity("Launcher_manifest_4b", true);
+            enableManifestActivity("Launcher_manifest_4a", false);
+
+            retryUntil(() -> getManager().getManifestShortcuts().size() == 0,
+                    "Manifest shortcuts didn't update");
+
+            // 3 was not inned, so gone.  But 1 and 2 remain.
+            assertWith(getManager().getManifestShortcuts())
+                    .isEmpty();
+            assertWith(getManager().getPinnedShortcuts())
+                    .haveIds("ms41", "ms42")
+                    .areAllDisabled()
+                    .forShortcutWithId("ms41", si -> {
+                        assertEquals(Intent.ACTION_VIEW, si.getIntent().getAction());
+                    })
+                    .forShortcutWithId("ms42", si -> {
+                        assertEquals(Intent.ACTION_VIEW, si.getIntent().getAction());
+                    })
+                    ;
+        });
+    }
+
+    // TODO Test auto rank adjustment.
+    // TODO Test save & load.
+}
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerCtsTestsBase.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerCtsTestsBase.java
new file mode 100644
index 0000000..f66fc31
--- /dev/null
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerCtsTestsBase.java
@@ -0,0 +1,523 @@
+/*
+ * 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.content.pm.cts.shortcutmanager;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.*;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.pm.LauncherApps.ShortcutQuery;
+import android.content.pm.PackageManager;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.os.UserHandle;
+import android.support.annotation.NonNull;
+import android.test.InstrumentationTestCase;
+import android.text.TextUtils;
+
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+
+public abstract class ShortcutManagerCtsTestsBase extends InstrumentationTestCase {
+
+    private static final boolean DUMPSYS_IN_TEARDOWN = false; // DO NOT SUBMIT WITH true
+
+    private static class SpoofingContext extends ContextWrapper {
+        private final String mPackageName;
+
+        public SpoofingContext(Context base, String packageName) {
+            super(base);
+            mPackageName = packageName;
+        }
+
+        @Override
+        public String getPackageName() {
+            return mPackageName;
+        }
+    }
+
+    protected static final SecureRandom sRandom = new SecureRandom();
+
+    private Context mCurrentCallerPackage;
+    private int mUserId;
+    private UserHandle mUserHandle;
+
+    private String mOriginalLauncher;
+
+    protected Context mPackageContext1;
+    protected Context mPackageContext2;
+    protected Context mPackageContext3;
+    protected Context mPackageContext4;
+
+    protected Context mLauncherContext1;
+    protected Context mLauncherContext2;
+    protected Context mLauncherContext3;
+    protected Context mLauncherContext4;
+
+    private LauncherApps mLauncherApps1;
+    private LauncherApps mLauncherApps2;
+    private LauncherApps mLauncherApps3;
+    private LauncherApps mLauncherApps4;
+
+    private Map<Context, ShortcutManager> mManagers = new HashMap<>();
+    private Map<Context, LauncherApps> mLauncherAppses = new HashMap<>();
+
+    private ShortcutManager mCurrentManager;
+    private LauncherApps mCurrentLauncherApps;
+
+    private static final String[] ACTIVITIES_WITH_MANIFEST_SHORTCUTS = {
+            "Launcher_manifest_1",
+            "Launcher_manifest_2",
+            "Launcher_manifest_3",
+            "Launcher_manifest_4a",
+            "Launcher_manifest_4b",
+            "Launcher_manifest_error_1",
+            "Launcher_manifest_error_2",
+            "Launcher_manifest_error_3"
+    };
+
+    private ComponentName mTargetActivityOverride;
+
+    private static class ShortcutActivity extends Activity {
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mUserId = getTestContext().getUserId();
+        mUserHandle = android.os.Process.myUserHandle();
+
+        resetConfig(getInstrumentation());
+        final String config = getOverrideConfig();
+        if (config != null) {
+            overrideConfig(getInstrumentation(), config);
+        }
+        mOriginalLauncher = getDefaultLauncher(getInstrumentation());
+
+        mPackageContext1 = new SpoofingContext(getTestContext(),
+                "android.content.pm.cts.shortcutmanager.packages.package1");
+        mPackageContext2 = new SpoofingContext(getTestContext(),
+                "android.content.pm.cts.shortcutmanager.packages.package2");
+        mPackageContext3 = new SpoofingContext(getTestContext(),
+                "android.content.pm.cts.shortcutmanager.packages.package3");
+        mPackageContext4 = new SpoofingContext(getTestContext(),
+                "android.content.pm.cts.shortcutmanager.packages.package4");
+        mLauncherContext1 = new SpoofingContext(getTestContext(),
+                "android.content.pm.cts.shortcutmanager.packages.launcher1");
+        mLauncherContext2 = new SpoofingContext(getTestContext(),
+                "android.content.pm.cts.shortcutmanager.packages.launcher2");
+        mLauncherContext3 = new SpoofingContext(getTestContext(),
+                "android.content.pm.cts.shortcutmanager.packages.launcher3");
+        mLauncherContext4 = new SpoofingContext(getTestContext(),
+                "android.content.pm.cts.shortcutmanager.packages.launcher4");
+
+        mLauncherApps1 = new LauncherApps(mLauncherContext1);
+        mLauncherApps2 = new LauncherApps(mLauncherContext2);
+        mLauncherApps3 = new LauncherApps(mLauncherContext3);
+        mLauncherApps4 = new LauncherApps(mLauncherContext4);
+
+        clearShortcuts(getInstrumentation(), mUserId, mPackageContext1.getPackageName());
+        clearShortcuts(getInstrumentation(), mUserId, mPackageContext2.getPackageName());
+        clearShortcuts(getInstrumentation(), mUserId, mPackageContext3.getPackageName());
+        clearShortcuts(getInstrumentation(), mUserId, mPackageContext4.getPackageName());
+
+        setCurrentCaller(mPackageContext1);
+
+        // Make sure shortcuts are removed.
+        withCallers(getAllPublishers(), () -> {
+            // Clear all shortcuts.
+            clearShortcuts(getInstrumentation(), mUserId, getCurrentCallingPackage());
+
+            disableActivitiesWithManifestShortucts();
+
+            assertEquals("for " + getCurrentCallingPackage(),
+                    0, getManager().getDynamicShortcuts().size());
+            assertEquals("for " + getCurrentCallingPackage(),
+                    0, getManager().getPinnedShortcuts().size());
+            assertEquals("for " + getCurrentCallingPackage(),
+                    0, getManager().getManifestShortcuts().size());
+        });
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (DUMPSYS_IN_TEARDOWN) {
+            dumpsysShortcut(getInstrumentation());
+        }
+
+        withCallers(getAllPublishers(), () -> disableActivitiesWithManifestShortucts());
+
+        resetConfig(getInstrumentation());
+
+        if (!TextUtils.isEmpty(mOriginalLauncher)) {
+            setDefaultLauncher(getInstrumentation(), mOriginalLauncher);
+        }
+
+        super.tearDown();
+    }
+
+    protected Context getTestContext() {
+        return getInstrumentation().getContext();
+    }
+
+    protected UserHandle getUserHandle() {
+        return mUserHandle;
+    }
+
+    protected List<Context> getAllPublishers() {
+        // 4 has a different signature, so we can't call for it.
+        return list(mPackageContext1, mPackageContext2, mPackageContext3);
+    }
+
+    protected List<Context> getAllLaunchers() {
+        // 4 has a different signature, so we can't call for it.
+        return list(mLauncherContext1, mLauncherContext2, mLauncherContext3);
+    }
+
+    protected List<Context> getAllCallers() {
+        return list(
+                mPackageContext1, mPackageContext2, mPackageContext3, mPackageContext4,
+                mLauncherContext1, mLauncherContext2, mLauncherContext3, mLauncherContext4);
+    }
+
+    protected ComponentName getActivity(String className) {
+        return new ComponentName(getCurrentCallingPackage(),
+                "android.content.pm.cts.shortcutmanager.packages." + className);
+
+    }
+
+    protected void disableActivitiesWithManifestShortucts() {
+        if (getManager().getManifestShortcuts().size() > 0) {
+            // Disable DISABLED_ACTIVITIES
+            for (String className : ACTIVITIES_WITH_MANIFEST_SHORTCUTS) {
+                enableManifestActivity(className, false);
+            }
+        }
+    }
+
+    protected void enableManifestActivity(String className, boolean enabled) {
+        getTestContext().getPackageManager().setComponentEnabledSetting(getActivity(className),
+                enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+                        : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+                PackageManager.DONT_KILL_APP);
+    }
+
+    protected void setTargetActivityOverride(String className) {
+        mTargetActivityOverride = getActivity(className);
+    }
+
+
+    protected void withCallers(List<Context> callers, Runnable r) {
+        for (Context c : callers) {
+            runWithCaller(c, r);
+        }
+    }
+
+    protected String getOverrideConfig() {
+        return null;
+    }
+
+    protected void setCurrentCaller(Context callerContext) {
+        mCurrentCallerPackage = callerContext;
+
+        if (!mManagers.containsKey(mCurrentCallerPackage)) {
+            mManagers.put(mCurrentCallerPackage, new ShortcutManager(mCurrentCallerPackage));
+        }
+        mCurrentManager = mManagers.get(mCurrentCallerPackage);
+
+        if (!mLauncherAppses.containsKey(mCurrentCallerPackage)) {
+            mLauncherAppses.put(mCurrentCallerPackage, new LauncherApps(mCurrentCallerPackage));
+        }
+        mCurrentLauncherApps = mLauncherAppses.get(mCurrentCallerPackage);
+
+        mTargetActivityOverride = null;
+    }
+
+    protected Context getCurrentCallerContext() {
+        return mCurrentCallerPackage;
+    }
+
+    protected String getCurrentCallingPackage() {
+        return getCurrentCallerContext().getPackageName();
+    }
+
+    protected ShortcutManager getManager() {
+        return mCurrentManager;
+    }
+
+    protected LauncherApps getLauncherApps() {
+        return mCurrentLauncherApps;
+    }
+
+    protected void runWithCaller(Context callerContext, Runnable r) {
+        final Context prev = mCurrentCallerPackage;
+
+        setCurrentCaller(callerContext);
+
+        r.run();
+
+        setCurrentCaller(prev);
+    }
+
+    public static Bundle makeBundle(Object... keysAndValues) {
+        assertTrue((keysAndValues.length % 2) == 0);
+
+        if (keysAndValues.length == 0) {
+            return null;
+        }
+        final Bundle ret = new Bundle();
+
+        for (int i = keysAndValues.length - 2; i >= 0; i -= 2) {
+            final String key = keysAndValues[i].toString();
+            final Object value = keysAndValues[i + 1];
+
+            if (value == null) {
+                ret.putString(key, null);
+            } else if (value instanceof Integer) {
+                ret.putInt(key, (Integer) value);
+            } else if (value instanceof String) {
+                ret.putString(key, (String) value);
+            } else if (value instanceof Bundle) {
+                ret.putBundle(key, (Bundle) value);
+            } else {
+                fail("Type not supported yet: " + value.getClass().getName());
+            }
+        }
+        return ret;
+    }
+
+    public static PersistableBundle makePersistableBundle(Object... keysAndValues) {
+        assertTrue((keysAndValues.length % 2) == 0);
+
+        if (keysAndValues.length == 0) {
+            return null;
+        }
+        final PersistableBundle ret = new PersistableBundle();
+
+        for (int i = keysAndValues.length - 2; i >= 0; i -= 2) {
+            final String key = keysAndValues[i].toString();
+            final Object value = keysAndValues[i + 1];
+
+            if (value == null) {
+                ret.putString(key, null);
+            } else if (value instanceof Integer) {
+                ret.putInt(key, (Integer) value);
+            } else if (value instanceof String) {
+                ret.putString(key, (String) value);
+            } else if (value instanceof PersistableBundle) {
+                ret.putPersistableBundle(key, (PersistableBundle) value);
+            } else {
+                fail("Type not supported yet: " + value.getClass().getName());
+            }
+        }
+        return ret;
+    }
+
+    /**
+     * Make a shortcut with an ID.
+     */
+    protected ShortcutInfo makeShortcut(String id) {
+        return makeShortcut(id, "Title-" + id);
+    }
+
+    protected ShortcutInfo makeShortcutWithRank(String id, int rank) {
+        return makeShortcut(
+                id, "Title-" + id, /* activity =*/ null, /* icon =*/ null,
+                makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), rank);
+    }
+
+    /**
+     * Make a shortcut with an ID and a title.
+     */
+    protected ShortcutInfo makeShortcut(String id, String shortLabel) {
+        return makeShortcut(
+                id, shortLabel, /* activity =*/ null, /* icon =*/ null,
+                makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0);
+    }
+
+    protected ShortcutInfo makeShortcut(String id, ComponentName activity) {
+        return makeShortcut(
+                id, "Title-" + id, activity, /* icon =*/ null,
+                makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0);
+    }
+
+    /**
+     * Make a shortcut with an ID and icon.
+     */
+    protected ShortcutInfo makeShortcutWithIcon(String id, Icon icon) {
+        return makeShortcut(
+                id, "Title-" + id, /* activity =*/ null, icon,
+                makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0);
+    }
+
+    /**
+     * Make multiple shortcuts with IDs.
+     */
+    protected List<ShortcutInfo> makeShortcuts(String... ids) {
+        final ArrayList<ShortcutInfo> ret = new ArrayList();
+        for (String id : ids) {
+            ret.add(makeShortcut(id));
+        }
+        return ret;
+    }
+
+    protected ShortcutInfo.Builder makeShortcutBuilder(String id) {
+        return new ShortcutInfo.Builder(getCurrentCallerContext(), id);
+    }
+
+    /**
+     * Make a shortcut with details.
+     */
+    protected ShortcutInfo makeShortcut(String id, String shortLabel, ComponentName activity,
+            Icon icon, Intent intent, int rank) {
+        final ShortcutInfo.Builder b = makeShortcutBuilder(id)
+                .setShortLabel(shortLabel)
+                .setRank(rank)
+                .setIntent(intent);
+        if (activity != null) {
+            b.setActivity(activity);
+        } else if (mTargetActivityOverride != null) {
+            b.setActivity(mTargetActivityOverride);
+        }
+        if (icon != null) {
+            b.setIcon(icon);
+        }
+        return b.build();
+    }
+
+    /**
+     * Make an intent.
+     */
+    protected Intent makeIntent(String action, Class<?> clazz, Object... bundleKeysAndValues) {
+        final Intent intent = new Intent(action);
+        intent.setComponent(makeComponent(clazz));
+        intent.replaceExtras(makeBundle(bundleKeysAndValues));
+        return intent;
+    }
+
+    /**
+     * Make an component name, with the client context.
+     */
+    @NonNull
+    protected ComponentName makeComponent(Class<?> clazz) {
+        return new ComponentName(getCurrentCallerContext(), clazz);
+    }
+
+    protected Drawable getIconAsLauncher(Context launcherContext, String packageName,
+            String shortcutId) {
+        return getIconAsLauncher(launcherContext, packageName, shortcutId, /* withBadge=*/ true);
+    }
+
+    protected Drawable getIconAsLauncher(Context launcherContext, String packageName,
+            String shortcutId, boolean withBadge) {
+        setDefaultLauncher(getInstrumentation(), launcherContext);
+
+        final AtomicReference<Drawable> ret = new AtomicReference<>();
+
+        runWithCaller(launcherContext, () -> {
+            final ShortcutQuery q = new ShortcutQuery()
+                    .setQueryFlags(ShortcutQuery.FLAG_MATCH_DYNAMIC
+                                    | ShortcutQuery.FLAG_MATCH_MANIFEST
+                                    | ShortcutQuery.FLAG_MATCH_PINNED
+                                    | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY)
+                    .setPackage(packageName)
+                    .setShortcutIds(list(shortcutId));
+            final List<ShortcutInfo> found = getLauncherApps().getShortcuts(q, getUserHandle());
+
+            assertEquals("Shortcut not found", 1, found.size());
+
+            if (withBadge) {
+                ret.set(getLauncherApps().getShortcutBadgedIconDrawable(found.get(0), 0));
+            } else {
+                ret.set(getLauncherApps().getShortcutIconDrawable(found.get(0), 0));
+            }
+        });
+        return ret.get();
+    }
+
+    protected void assertIconDimensions(Context launcherContext, String packageName,
+            String shortcutId, Icon expectedIcon) {
+        final Drawable actual = getIconAsLauncher(launcherContext, packageName, shortcutId);
+        if (actual == null && expectedIcon == null) {
+            return; // okay
+        }
+        final Drawable expected = expectedIcon.loadDrawable(getTestContext());
+        assertEquals(expected.getIntrinsicWidth(), actual.getIntrinsicWidth());
+        assertEquals(expected.getIntrinsicHeight(), actual.getIntrinsicHeight());
+    }
+
+    protected void assertIconDimensions(Icon expectedIcon, Drawable actual) {
+        if (actual == null && expectedIcon == null) {
+            return; // okay
+        }
+        final Drawable expected = expectedIcon.loadDrawable(getTestContext());
+
+        assertEquals(expected.getIntrinsicWidth(), actual.getIntrinsicWidth());
+        assertEquals(expected.getIntrinsicHeight(), actual.getIntrinsicHeight());
+    }
+
+    protected Icon loadPackageDrawableIcon(Context packageContext, String resName)
+            throws Exception {
+        final Resources res = getTestContext().getPackageManager().getResourcesForApplication(
+                packageContext.getPackageName());
+
+        // Note the resource package names don't have the numbers.
+        final int id = res.getIdentifier(resName, "drawable",
+                "android.content.pm.cts.shortcutmanager.packages");
+        if (id == 0) {
+            fail("Drawable " + resName + " is not found in package "
+                    + packageContext.getPackageName());
+        }
+        return Icon.createWithResource(packageContext, id);
+    }
+
+    protected Icon loadCallerDrawableIcon(String resName) throws Exception {
+        return loadPackageDrawableIcon(getCurrentCallerContext(), resName);
+    }
+
+    protected List<ShortcutInfo> getShortcutsAsLauncher(
+            int flags, String packageName, String activityName,
+            long changedSince, List<String> ids) {
+        final ShortcutQuery q = new ShortcutQuery();
+        q.setQueryFlags(flags);
+        if (packageName != null) {
+            q.setPackage(packageName);
+            if (activityName != null) {
+                q.setActivity(new ComponentName(packageName,
+                        "android.content.pm.cts.shortcutmanager.packages." + activityName));
+            }
+        }
+        q.setChangedSince(changedSince);
+        if (ids != null && ids.size() > 0) {
+            q.setShortcutIds(ids);
+        }
+        return getLauncherApps().getShortcuts(q, getUserHandle());
+    }
+}
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerLauncherApiTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerLauncherApiTest.java
new file mode 100644
index 0000000..25a3831
--- /dev/null
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerLauncherApiTest.java
@@ -0,0 +1,349 @@
+/*
+ * 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.content.pm.cts.shortcutmanager;
+
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_MANIFEST;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.retryUntil;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.setDefaultLauncher;
+
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.Icon;
+import android.test.suitebuilder.annotation.SmallTest;
+
+@SmallTest
+public class ShortcutManagerLauncherApiTest extends ShortcutManagerCtsTestsBase {
+    public void testPinShortcuts() {
+        runWithCaller(mPackageContext1, () -> {
+            enableManifestActivity("Launcher_manifest_1", true);
+            enableManifestActivity("Launcher_manifest_2", true);
+
+            retryUntil(() -> getManager().getManifestShortcuts().size() > 0,
+                    "Manifest shortcuts didn't show up");
+
+            assertTrue(getManager().setDynamicShortcuts(list(
+                    makeShortcut("s1"),
+                    makeShortcut("s2"),
+                    makeShortcut("s3"),
+                    makeShortcut("s4"),
+                    makeShortcut("s5")
+            )));
+            assertWith(getManager().getDynamicShortcuts())
+                    .haveIds("s1", "s2", "s3", "s4", "s5")
+                    .areAllDynamic()
+                    .areAllEnabled();
+        });
+        runWithCaller(mPackageContext2, () -> {
+            enableManifestActivity("Launcher_manifest_1", true);
+            enableManifestActivity("Launcher_manifest_3", true);
+
+            retryUntil(() -> getManager().getManifestShortcuts().size() > 0,
+                    "Manifest shortcuts didn't show up");
+
+            assertTrue(getManager().setDynamicShortcuts(list(
+                    makeShortcut("s1"),
+                    makeShortcut("s2"),
+                    makeShortcut("s3"),
+                    makeShortcut("s4"),
+                    makeShortcut("s5")
+            )));
+            assertWith(getManager().getDynamicShortcuts())
+                    .haveIds("s1", "s2", "s3", "s4", "s5")
+                    .areAllDynamic()
+                    .areAllEnabled();
+        });
+
+        setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+        runWithCaller(mLauncherContext1, () -> {
+            getLauncherApps().pinShortcuts(
+                    mPackageContext1.getPackageName(),
+                    list("s1", "s2", "s3", "ms1", "ms21"), getUserHandle());
+            getLauncherApps().pinShortcuts(
+                    mPackageContext2.getPackageName(),
+                    list("s2", "s3", "ms1", "ms31"), getUserHandle());
+        });
+
+        runWithCaller(mPackageContext1, () -> {
+            getManager().removeDynamicShortcuts(list("s1", "s2"));
+        });
+
+        runWithCaller(mPackageContext2, () -> {
+            enableManifestActivity("Launcher_manifest_3", false);
+
+            retryUntil(() -> getManager().getManifestShortcuts().size() == 1,
+                    "Manifest shortcuts didn't updated");
+
+            getManager().removeDynamicShortcuts(list("s1", "s2"));
+        });
+
+        // Check the result.
+        runWithCaller(mPackageContext1, () -> {
+            assertWith(getManager().getDynamicShortcuts())
+                    .haveIds("s3", "s4", "s5")
+                    .areAllEnabled();
+            assertWith(getManager().getManifestShortcuts())
+                    .haveIds("ms1", "ms21", "ms22")
+                    .areAllEnabled();
+            assertWith(getManager().getPinnedShortcuts())
+                    .haveIds("s1", "s2", "s3", "ms1", "ms21")
+                    .areAllEnabled();
+        });
+
+        runWithCaller(mPackageContext2, () -> {
+            assertWith(getManager().getDynamicShortcuts())
+                    .haveIds("s3", "s4", "s5")
+                    .areAllEnabled();
+            assertWith(getManager().getManifestShortcuts())
+                    .haveIds("ms1")
+                    .areAllEnabled();
+            assertWith(getManager().getPinnedShortcuts())
+                    .haveIds("s2", "s3", "ms1", "ms31")
+
+                    .selectByIds("s2", "s3", "ms1")
+                    .areAllEnabled()
+
+                    .revertToOriginalList()
+                    .selectByIds("ms31")
+                    .areAllDisabled();
+        });
+    }
+
+    public void testGetShortcuts() throws Exception {
+
+        testPinShortcuts();
+
+        Thread.sleep(2);
+        final long time1 = System.currentTimeMillis();
+
+        runWithCaller(mPackageContext1, () -> {
+            assertTrue(getManager().updateShortcuts(list(
+                    makeShortcut("s3"))));
+
+            setTargetActivityOverride("Launcher_manifest_2");
+
+            assertTrue(getManager().updateShortcuts(list(
+                    makeShortcut("s1"),
+                    makeShortcut("s5"))));
+        });
+
+        Thread.sleep(2);
+        final long time2 = System.currentTimeMillis();
+
+        runWithCaller(mPackageContext1, () -> {
+            assertTrue(getManager().updateShortcuts(list(
+                    makeShortcutWithRank("s4", 999))));
+        });
+
+        runWithCaller(mPackageContext2, () -> {
+            setTargetActivityOverride("Launcher_manifest_1");
+
+            assertTrue(getManager().updateShortcuts(list(
+                    makeShortcut("s1"))));
+        });
+
+        Thread.sleep(2);
+        final long time3 = System.currentTimeMillis();
+
+        runWithCaller(mLauncherContext1, () -> {
+            assertWith(getShortcutsAsLauncher(
+                    FLAG_MATCH_DYNAMIC,
+                    mPackageContext1.getPackageName(),
+                    null,
+                    0,
+                    list()))
+                    .haveIds("s3", "s4", "s5")
+                    .areAllNotWithKeyFieldsOnly();
+            assertWith(getShortcutsAsLauncher(
+                    FLAG_MATCH_PINNED,
+                    mPackageContext1.getPackageName(),
+                    null,
+                    0,
+                    list()))
+                    .haveIds("s1", "s2", "s3", "ms1", "ms21")
+                    .areAllNotWithKeyFieldsOnly();
+            assertWith(getShortcutsAsLauncher(
+                    FLAG_MATCH_MANIFEST,
+                    mPackageContext1.getPackageName(),
+                    null,
+                    0,
+                    list()))
+                    .haveIds("ms1", "ms21", "ms22")
+                    .areAllNotWithKeyFieldsOnly();
+
+            assertWith(getShortcutsAsLauncher(
+                    FLAG_MATCH_DYNAMIC | FLAG_GET_KEY_FIELDS_ONLY,
+                    mPackageContext1.getPackageName(),
+                    null,
+                    0,
+                    list()))
+                    .haveIds("s3", "s4", "s5")
+                    .areAllWithKeyFieldsOnly();
+
+            assertWith(getShortcutsAsLauncher(
+                    FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED,
+                    mPackageContext1.getPackageName(),
+                    null,
+                    0,
+                    list()))
+                    .haveIds("s3", "s4", "s5", "s1", "s2", "s3", "ms1", "ms21")
+                    .areAllNotWithKeyFieldsOnly();
+
+            assertWith(getShortcutsAsLauncher(
+                    FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST,
+                    mPackageContext1.getPackageName(),
+                    null,
+                    0,
+                    list()))
+                    .haveIds("s1", "s2", "s3", "ms1", "ms21", "ms1", "ms21", "ms22")
+                    .areAllNotWithKeyFieldsOnly();
+            assertWith(getShortcutsAsLauncher(
+                    FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST,
+                    mPackageContext1.getPackageName(),
+                    null,
+                    0,
+                    list()))
+                    .haveIds("s3", "s4", "s5", "s1", "s2", "ms1", "ms21", "ms1", "ms21", "ms22")
+                    .areAllNotWithKeyFieldsOnly();
+            assertWith(getShortcutsAsLauncher(
+                    FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST
+                            | FLAG_GET_KEY_FIELDS_ONLY,
+                    mPackageContext1.getPackageName(),
+                    null,
+                    0,
+                    list()))
+                    .haveIds("s3", "s4", "s5", "s1", "s2", "ms1", "ms21", "ms1", "ms21", "ms22")
+                    .areAllWithKeyFieldsOnly();
+
+
+            // Package 2
+
+            assertWith(getShortcutsAsLauncher(
+                    FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST,
+                    mPackageContext2.getPackageName(),
+                    null,
+                    0,
+                    list()))
+                    .haveIds("s2", "s3", "s4", "s5", "ms1", "ms31")
+                    ;
+
+            // With activity
+            assertWith(getShortcutsAsLauncher(
+                    FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST,
+                    mPackageContext1.getPackageName(),
+                    "Launcher_manifest_2",
+                    0,
+                    list()))
+                    .haveIds("ms21", "ms22", "s1", "s5")
+                    .areAllNotWithKeyFieldsOnly();
+
+            // With ids
+            assertWith(getShortcutsAsLauncher(
+                    FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST,
+                    mPackageContext1.getPackageName(),
+                    null,
+                    0,
+                    list("s1", "s2", "ms1")))
+                    .haveIds("s1", "s2", "ms1")
+                    .areAllNotWithKeyFieldsOnly();
+
+            // With time.
+            assertWith(getShortcutsAsLauncher(
+                    FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST,
+                    mPackageContext1.getPackageName(),
+                    null,
+                    time2,
+                    list()))
+                    .haveIds("s4")
+                    .areAllNotWithKeyFieldsOnly();
+
+            // No shortcuts have changed since time3.
+            assertWith(getShortcutsAsLauncher(
+                    FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST,
+                    mPackageContext1.getPackageName(),
+                    null,
+                    time3,
+                    list()))
+                    .isEmpty();
+            assertWith(getShortcutsAsLauncher(
+                    FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST,
+                    mPackageContext2.getPackageName(),
+                    null,
+                    time3,
+                    list()))
+                    .isEmpty();
+        });
+    }
+
+    public void testGetShortcutIcon() throws Exception {
+        final Icon icon1 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+                getTestContext().getResources(), R.drawable.black_16x64));
+        final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+                getTestContext().getResources(), R.drawable.black_32x32));
+        final Icon icon3 = loadPackageDrawableIcon(mPackageContext1, "black_64x16");
+        final Icon icon4 = loadPackageDrawableIcon(mPackageContext1, "black_64x64");
+
+        final Icon icon5 = loadPackageDrawableIcon(mPackageContext1, "black_16x16");
+
+        runWithCaller(mPackageContext1, () -> {
+            enableManifestActivity("Launcher_manifest_2", true);
+
+            retryUntil(() -> getManager().getManifestShortcuts().size() > 0,
+                    "Manifest shortcuts didn't show up");
+
+            assertTrue(getManager().setDynamicShortcuts(list(
+                    makeShortcutWithIcon("s1", icon1),
+                    makeShortcutWithIcon("s2", icon2),
+                    makeShortcutWithIcon("s3", icon3),
+                    makeShortcutWithIcon("s4", icon4))));
+        });
+
+        setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+        assertIconDimensions(icon1, getIconAsLauncher(
+                mLauncherContext1, mPackageContext1.getPackageName(), "s1", true));
+        assertIconDimensions(icon2, getIconAsLauncher(
+                mLauncherContext1, mPackageContext1.getPackageName(), "s2", true));
+        assertIconDimensions(icon3, getIconAsLauncher(
+                mLauncherContext1, mPackageContext1.getPackageName(), "s3", true));
+        assertIconDimensions(icon4, getIconAsLauncher(
+                mLauncherContext1, mPackageContext1.getPackageName(), "s4", true));
+
+        assertIconDimensions(icon5, getIconAsLauncher(
+                mLauncherContext1, mPackageContext1.getPackageName(), "ms21", true));
+
+        assertIconDimensions(icon1, getIconAsLauncher(
+                mLauncherContext1, mPackageContext1.getPackageName(), "s1", false));
+        assertIconDimensions(icon2, getIconAsLauncher(
+                mLauncherContext1, mPackageContext1.getPackageName(), "s2", false));
+        assertIconDimensions(icon3, getIconAsLauncher(
+                mLauncherContext1, mPackageContext1.getPackageName(), "s3", false));
+        assertIconDimensions(icon4, getIconAsLauncher(
+                mLauncherContext1, mPackageContext1.getPackageName(), "s4", false));
+
+        assertIconDimensions(icon5, getIconAsLauncher(
+                mLauncherContext1, mPackageContext1.getPackageName(), "ms21", false));
+    }
+
+    // TODO Callback test
+
+    // TODO Launch test
+}
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMaxCountTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMaxCountTest.java
new file mode 100644
index 0000000..3e75b4a
--- /dev/null
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMaxCountTest.java
@@ -0,0 +1,302 @@
+/*
+ * 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.content.pm.cts.shortcutmanager;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertDynamicShortcutCountExceeded;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.retryUntil;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.setDefaultLauncher;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+@SmallTest
+public class ShortcutManagerMaxCountTest extends ShortcutManagerCtsTestsBase {
+    /**
+     * Basic tests: single app, single activity, no manifest shortcuts.
+     */
+    public void testNumDynamicShortcuts() {
+        runWithCaller(mPackageContext1, () -> {
+            assertTrue(getManager().setDynamicShortcuts(list(makeShortcut("s1"))));
+            assertTrue(getManager().setDynamicShortcuts(list(
+                    makeShortcut("s1"),
+                    makeShortcut("s2"),
+                    makeShortcut("s3"),
+                    makeShortcut("s4"),
+                    makeShortcut("s5")
+            )));
+            assertWith(getManager().getDynamicShortcuts())
+                    .haveIds("s1", "s2", "s3", "s4", "s5")
+                    .areAllDynamic()
+                    .areAllEnabled();
+            assertTrue(getManager().setDynamicShortcuts(list(
+                    makeShortcut("s1x"),
+                    makeShortcut("s2x"),
+                    makeShortcut("s3x"),
+                    makeShortcut("s4x"),
+                    makeShortcut("s5x")
+            )));
+
+            assertDynamicShortcutCountExceeded(() -> {
+                getManager().setDynamicShortcuts(list(
+                        makeShortcut("s1y"),
+                        makeShortcut("s2y"),
+                        makeShortcut("s3y"),
+                        makeShortcut("s4y"),
+                        makeShortcut("s5y"),
+                        makeShortcut("s6y")));
+            });
+            assertWith(getManager().getDynamicShortcuts())
+                    .haveIds("s1x", "s2x", "s3x", "s4x", "s5x")
+                    .areAllDynamic()
+                    .areAllEnabled();
+
+            assertDynamicShortcutCountExceeded(() -> {
+                getManager().addDynamicShortcuts(list(
+                        makeShortcut("s1y")));
+            });
+            assertWith(getManager().getDynamicShortcuts())
+                    .haveIds("s1x", "s2x", "s3x", "s4x", "s5x")
+                    .areAllDynamic()
+                    .areAllEnabled();
+            getManager().removeDynamicShortcuts(list("s5x"));
+            assertTrue(getManager().addDynamicShortcuts(list(
+                    makeShortcut("s1y"))));
+
+            assertWith(getManager().getDynamicShortcuts())
+                    .haveIds("s1x", "s2x", "s3x", "s4x", "s1y")
+                    .areAllDynamic()
+                    .areAllEnabled();
+
+            getManager().removeAllDynamicShortcuts();
+
+            assertTrue(getManager().addDynamicShortcuts(list(
+                    makeShortcut("s1"),
+                    makeShortcut("s2"),
+                    makeShortcut("s3"),
+                    makeShortcut("s4"),
+                    makeShortcut("s5")
+            )));
+            assertWith(getManager().getDynamicShortcuts())
+                    .haveIds("s1", "s2", "s3", "s4", "s5")
+                    .areAllDynamic()
+                    .areAllEnabled();
+        });
+    }
+
+    /**
+     * Manifest shortcuts are included in the count too.
+     */
+    public void testWithManifest() throws Exception {
+        runWithCaller(mPackageContext1, () -> {
+            enableManifestActivity("Launcher_manifest_1", true);
+            enableManifestActivity("Launcher_manifest_2", true);
+
+            retryUntil(() -> getManager().getManifestShortcuts().size() == 3,
+                    "Manifest shortcuts didn't show up");
+
+        });
+
+        runWithCaller(mPackageContext1, () -> {
+            assertWith(getManager().getManifestShortcuts())
+                    .haveIds("ms1", "ms21", "ms22")
+                    .areAllManifest()
+                    .areAllEnabled()
+                    .areAllNotPinned()
+
+                    .selectByIds("ms1")
+                    .forAllShortcuts(sa -> {
+                        assertEquals(getActivity("Launcher_manifest_1"), sa.getActivity());
+                    })
+
+                    .revertToOriginalList()
+                    .selectByIds("ms21", "ms22")
+                    .forAllShortcuts(sa -> {
+                        assertEquals(getActivity("Launcher_manifest_2"), sa.getActivity());
+                    });
+
+        });
+
+        // Note since max counts is per activity, testNumDynamicShortcuts_single should just pass.
+        testNumDynamicShortcuts();
+
+        // Launcher_manifest_1 has one manifest, so can only add 4 dynamic shortcuts.
+        runWithCaller(mPackageContext1, () -> {
+            setTargetActivityOverride("Launcher_manifest_1");
+
+            assertTrue(getManager().setDynamicShortcuts(list(
+                    makeShortcut("s1"),
+                    makeShortcut("s2"),
+                    makeShortcut("s3"),
+                    makeShortcut("s4")
+            )));
+            assertWith(getManager().getDynamicShortcuts())
+                    .selectByActivity(getActivity("Launcher_manifest_1"))
+                    .haveIds("s1", "s2", "s3", "s4")
+                    .areAllEnabled();
+
+            assertDynamicShortcutCountExceeded(() -> getManager().setDynamicShortcuts(list(
+                    makeShortcut("s1x"),
+                    makeShortcut("s2x"),
+                    makeShortcut("s3x"),
+                    makeShortcut("s4x"),
+                    makeShortcut("s5x")
+            )));
+            // Not changed.
+            assertWith(getManager().getDynamicShortcuts())
+                    .selectByActivity(getActivity("Launcher_manifest_1"))
+                    .haveIds("s1", "s2", "s3", "s4")
+                    .areAllEnabled();
+        });
+
+        // Launcher_manifest_2 has two manifests, so can only add 3.
+        runWithCaller(mPackageContext1, () -> {
+            setTargetActivityOverride("Launcher_manifest_2");
+
+            assertTrue(getManager().addDynamicShortcuts(list(
+                    makeShortcut("s1"),
+                    makeShortcut("s2"),
+                    makeShortcut("s3")
+            )));
+            assertWith(getManager().getDynamicShortcuts())
+                    .selectByActivity(getActivity("Launcher_manifest_2"))
+                    .haveIds("s1", "s2", "s3")
+                    .areAllEnabled();
+
+            assertDynamicShortcutCountExceeded(() -> getManager().addDynamicShortcuts(list(
+                    makeShortcut("s1x")
+            )));
+            // Not added.
+            assertWith(getManager().getDynamicShortcuts())
+                    .selectByActivity(getActivity("Launcher_manifest_2"))
+                    .haveIds("s1", "s2", "s3")
+                    .areAllEnabled();
+        });
+    }
+
+    public void testChangeActivity() {
+        runWithCaller(mPackageContext1, () -> {
+            setTargetActivityOverride("Launcher");
+            assertTrue(getManager().setDynamicShortcuts(list(
+                    makeShortcut("s1"),
+                    makeShortcut("s2"),
+                    makeShortcut("s3"),
+                    makeShortcut("s4"),
+                    makeShortcut("s5")
+            )));
+            assertWith(getManager().getDynamicShortcuts())
+                    .selectByActivity(getActivity("Launcher"))
+                    .haveIds("s1", "s2", "s3", "s4", "s5")
+                    .areAllDynamic()
+                    .areAllEnabled();
+
+            setTargetActivityOverride("Launcher2");
+
+            assertTrue(getManager().addDynamicShortcuts(list(
+                    makeShortcut("s1b"),
+                    makeShortcut("s2b"),
+                    makeShortcut("s3b"),
+                    makeShortcut("s4b"),
+                    makeShortcut("s5b")
+            )));
+            assertWith(getManager().getDynamicShortcuts())
+                    .selectByActivity(getActivity("Launcher"))
+                    .haveIds("s1", "s2", "s3", "s4", "s5")
+                    .areAllDynamic()
+                    .areAllEnabled()
+
+                    .revertToOriginalList()
+                    .selectByActivity(getActivity("Launcher2"))
+                    .haveIds("s1b", "s2b", "s3b", "s4b", "s5b")
+                    .areAllDynamic()
+                    .areAllEnabled();
+
+            // Moving one from L1 to L2 is not allowed.
+            assertDynamicShortcutCountExceeded(() -> getManager().updateShortcuts(list(
+                    makeShortcut("s1", getActivity("Launcher2"))
+            )));
+
+            assertWith(getManager().getDynamicShortcuts())
+                    .selectByActivity(getActivity("Launcher"))
+                    .haveIds("s1", "s2", "s3", "s4", "s5")
+                    .areAllDynamic()
+                    .areAllEnabled()
+
+                    .revertToOriginalList()
+                    .selectByActivity(getActivity("Launcher2"))
+                    .haveIds("s1b", "s2b", "s3b", "s4b", "s5b")
+                    .areAllDynamic()
+                    .areAllEnabled();
+
+            // But swapping shortcuts will work.
+            assertTrue(getManager().updateShortcuts(list(
+                    makeShortcut("s1", getActivity("Launcher2")),
+                    makeShortcut("s1b", getActivity("Launcher"))
+            )));
+
+            assertWith(getManager().getDynamicShortcuts())
+                    .selectByActivity(getActivity("Launcher"))
+                    .haveIds("s1b", "s2", "s3", "s4", "s5")
+                    .areAllDynamic()
+                    .areAllEnabled()
+
+                    .revertToOriginalList()
+                    .selectByActivity(getActivity("Launcher2"))
+                    .haveIds("s1", "s2b", "s3b", "s4b", "s5b")
+                    .areAllDynamic()
+                    .areAllEnabled();
+        });
+    }
+
+    public void testWithPinned() {
+        runWithCaller(mPackageContext1, () -> {
+            assertTrue(getManager().setDynamicShortcuts(list(
+                    makeShortcut("s1"),
+                    makeShortcut("s2"),
+                    makeShortcut("s3"),
+                    makeShortcut("s4"),
+                    makeShortcut("s5")
+            )));
+        });
+
+        setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+        runWithCaller(mLauncherContext1, () -> {
+            getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
+                    list("s1", "s2", "s3", "s4", "s5"), getUserHandle());
+        });
+
+        runWithCaller(mPackageContext1, () -> {
+            assertTrue(getManager().setDynamicShortcuts(list(
+                    makeShortcut("s6"),
+                    makeShortcut("s7"),
+                    makeShortcut("s8"),
+                    makeShortcut("s9"),
+                    makeShortcut("s10")
+            )));
+
+            assertWith(getManager().getDynamicShortcuts())
+                    .haveIds("s6", "s7", "s8", "s9", "s10")
+                    .areAllEnabled()
+                    .areAllNotPinned();
+
+            assertWith(getManager().getPinnedShortcuts())
+                    .haveIds("s1", "s2", "s3", "s4", "s5")
+                    .areAllEnabled()
+                    .areAllNotDynamic();
+        });
+    }
+}
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMiscTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMiscTest.java
new file mode 100644
index 0000000..95defb8
--- /dev/null
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMiscTest.java
@@ -0,0 +1,43 @@
+/*
+ * 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.content.pm.cts.shortcutmanager;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.getIconSize;
+
+import android.content.pm.ShortcutManager;
+import android.test.suitebuilder.annotation.SmallTest;
+
+@SmallTest
+public class ShortcutManagerMiscTest extends ShortcutManagerCtsTestsBase {
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+    }
+
+    public void testMiscApis() throws Exception {
+        ShortcutManager manager = getTestContext().getSystemService(ShortcutManager.class);
+
+        assertEquals(5, manager.getMaxShortcutCountPerActivity());
+
+        // during the test, this process always considered to be in the foreground.
+        assertFalse(manager.isRateLimitingActive());
+
+        final int iconDimension = getIconSize(getInstrumentation());
+        assertEquals(iconDimension, manager.getIconMaxWidth());
+        assertEquals(iconDimension, manager.getIconMaxHeight());
+    }
+}
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerNegativeTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerNegativeTest.java
new file mode 100644
index 0000000..c711518
--- /dev/null
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerNegativeTest.java
@@ -0,0 +1,164 @@
+/*
+ * 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.content.pm.cts.shortcutmanager;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.concatResult;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+
+import android.content.pm.ShortcutManager;
+import android.test.MoreAsserts;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+
+@SmallTest
+public class ShortcutManagerNegativeTest extends ShortcutManagerCtsTestsBase {
+    private static final String TAG = "ShortcutNegativeCTS";
+
+    /**
+     * If true, reflection errors such as "field not found" will be a failure.  This is useful
+     * during development, but should be turned off in the actual code, since these fields/methods
+     * don't have to exist.
+     */
+    private static final boolean DISALLOW_REFLECTION_ERROR = false; // don't submit with true
+
+    private static Object readField(Object instance, String field)
+            throws NoSuchFieldException, IllegalAccessException {
+        final Field f = instance.getClass().getDeclaredField(field);
+        f.setAccessible(true);
+        final Object ret = f.get(instance);
+        if (ret == null) {
+            throw new NoSuchFieldError();
+        }
+        return ret;
+    }
+
+    private static void callMethodExpectingSecurityException(Object instance, String name,
+            String expectedMessage, Object... args)
+            throws NoSuchMethodException, IllegalAccessException {
+
+        Method m = null;
+        for (Method method : instance.getClass().getDeclaredMethods()) {
+            if (method.getName().equals(name)) {
+                m = method;
+                break;
+            }
+        }
+        if (m == null) {
+            throw new NoSuchMethodError();
+        }
+
+        m.setAccessible(true);
+        try {
+            m.invoke(instance, args);
+        } catch (InvocationTargetException e) {
+            if (e.getTargetException() instanceof SecurityException) {
+                MoreAsserts.assertContainsRegex(expectedMessage,
+                        e.getTargetException().getMessage());
+                return; // Pass
+            }
+        }
+        fail("Didn't throw exception");
+    }
+
+    private void checkAidlCall(String method, String expectedMessage, Object... args)
+            throws IllegalAccessException {
+        final ShortcutManager manager = getTestContext().getSystemService(ShortcutManager.class);
+
+        try {
+            callMethodExpectingSecurityException(readField(manager, "mService"), method,
+                    expectedMessage, args);
+        } catch (NoSuchFieldException|NoSuchMethodException e) {
+            if (DISALLOW_REFLECTION_ERROR) {
+                throw new RuntimeException(e);
+            } else {
+                Log.w(TAG, "Reflection failed, which is okay", e);
+            }
+        }
+    }
+
+    /**
+     * Make sure the internal AIDL methods are protected.
+     */
+    public void testDirectAidlCalls() throws IllegalAccessException {
+        checkAidlCall("resetThrottling", "Caller must be");
+
+        checkAidlCall("onApplicationActive", "does not have",
+                "package", getTestContext().getUserId());
+
+        checkAidlCall("getBackupPayload", "Caller must be", getTestContext().getUserId());
+
+        checkAidlCall("applyRestore", "Caller must be", null, getTestContext().getUserId());
+    }
+
+    private String runCommand(String command) throws IOException, InterruptedException {
+        final Process p = Runtime.getRuntime().exec(command);
+
+        final ArrayList<String> ret = new ArrayList<>();
+        try (BufferedReader r = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
+            String line;
+            while ((line = r.readLine()) != null) {
+                ret.add(line);
+            }
+        };
+        p.waitFor();
+        return concatResult(ret);
+    }
+
+    /**
+     * Make sure dumpsys shortcut can't be called.
+     */
+    public void testDump() throws Exception {
+        MoreAsserts.assertContainsRegex(
+                "can't dump by this caller", runCommand("dumpsys shortcut"));
+    }
+
+    /**
+     * Make sure cmd shortcut can't be called.
+     */
+    public void testCommand() throws Exception {
+        runWithCaller(mPackageContext1, () -> {
+            assertTrue(getManager().setDynamicShortcuts(list(
+                    makeShortcut("s1"))));
+        });
+
+        // cmd shortcut will fail silently, with no error outputs.
+        MoreAsserts.assertNotContainsRegex("Success", runCommand("cmd shortcut clear-shortcuts " +
+                mPackageContext1.getPackageName()));
+
+        // Shortcuts shouldn't be cleared.
+        runWithCaller(mPackageContext1, () -> {
+            assertWith(getManager().getDynamicShortcuts())
+                    .haveIds("s1");
+        });
+    }
+
+    /**
+     * Make sure AIDL methods can't be called for other users.
+     */
+    public void testUserIdSpoofing() throws IllegalAccessException {
+        checkAidlCall("getDynamicShortcuts", "Invalid user-ID",
+                mPackageContext1.getPackageName(), /* user-id*/ 10);
+    }
+}
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerSpoofDetectionTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerSpoofDetectionTest.java
new file mode 100644
index 0000000..64595f8
--- /dev/null
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerSpoofDetectionTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcutmanager;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertExpectException;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+
+import static org.mockito.Mockito.mock;
+
+import android.content.Context;
+import android.content.pm.LauncherApps;
+import android.content.pm.LauncherApps.ShortcutQuery;
+import android.os.Handler;
+import android.os.Looper;
+import android.test.suitebuilder.annotation.SmallTest;
+
+/**
+ * Tests to make sure the service will detect it when a caller is spoofing the package name
+ * to a package name with a different UID.
+ */
+@SmallTest
+public class ShortcutManagerSpoofDetectionTest extends ShortcutManagerCtsTestsBase {
+
+    @Override
+    protected String getOverrideConfig() {
+        return "max_shortcuts=10";
+    }
+
+    public void assertCallingPackageMismatch(String method, Context callerContext, Runnable r) {
+        assertExpectException(
+                "Caller=" + callerContext.getPackageName() + ", method=" + method,
+                SecurityException.class, "Calling package name mismatch",
+                () -> runWithCaller(callerContext, () -> r.run())
+        );
+    }
+
+    public void testPublisherSpoofing() {
+        assertCallingPackageMismatch("setDynamicShortcuts", mPackageContext4, () -> {
+            getManager().setDynamicShortcuts(list(makeShortcut("s1")));
+        });
+        assertCallingPackageMismatch("addDynamicShortcut", mPackageContext4, () -> {
+            getManager().addDynamicShortcuts(list(makeShortcut("s1")));
+        });
+        assertCallingPackageMismatch("deleteDynamicShortcut", mPackageContext4, () -> {
+            getManager().removeDynamicShortcuts(list("s1"));
+        });
+        assertCallingPackageMismatch("deleteAllDynamicShortcuts", mPackageContext4, () -> {
+            getManager().removeAllDynamicShortcuts();
+        });
+        assertCallingPackageMismatch("getDynamicShortcuts", mPackageContext4, () -> {
+            getManager().getDynamicShortcuts();
+        });
+        assertCallingPackageMismatch("getPinnedShortcuts", mPackageContext4, () -> {
+            getManager().getPinnedShortcuts();
+        });
+        assertCallingPackageMismatch("updateShortcuts", mPackageContext4, () -> {
+            getManager().updateShortcuts(list(makeShortcut("s1")));
+        });
+        assertCallingPackageMismatch("getMaxShortcutCountPerActivity", mPackageContext4, () -> {
+            getManager().getMaxShortcutCountPerActivity();
+        });
+        assertCallingPackageMismatch("getIconMaxWidth", mPackageContext4, () -> {
+            getManager().getIconMaxWidth();
+        });
+        assertCallingPackageMismatch("getIconMaxHeight", mPackageContext4, () -> {
+            getManager().getIconMaxHeight();
+        });
+    }
+
+    public void testLauncherSpoofing() {
+        assertCallingPackageMismatch("hasShortcutHostPermission", mLauncherContext4, () -> {
+            getLauncherApps().hasShortcutHostPermission();
+        });
+
+        assertCallingPackageMismatch("registerCallback", mLauncherContext4, () -> {
+            final LauncherApps.Callback c = mock(LauncherApps.Callback.class);
+            getLauncherApps().registerCallback(c, new Handler(Looper.getMainLooper()));
+        });
+
+        assertCallingPackageMismatch("getShortcuts", mLauncherContext4, () -> {
+            ShortcutQuery q = new ShortcutQuery();
+            getLauncherApps().getShortcuts(q, getUserHandle());
+        });
+
+        assertCallingPackageMismatch("pinShortcuts", mLauncherContext4, () -> {
+            getLauncherApps().pinShortcuts(
+                    mPackageContext1.getPackageName(), list(), getUserHandle());
+        });
+
+        assertCallingPackageMismatch("startShortcut 1", mLauncherContext4, () -> {
+            getLauncherApps().startShortcut(makeShortcut("s"), null, null);
+        });
+        assertCallingPackageMismatch("startShortcut 2", mLauncherContext4, () -> {
+            getLauncherApps().startShortcut(mPackageContext1.getPackageName(), "s1",
+                    null, null, getUserHandle());
+        });
+    }
+}
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerSpoofingTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerSpoofingTest.java
new file mode 100644
index 0000000..f7279fc
--- /dev/null
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerSpoofingTest.java
@@ -0,0 +1,123 @@
+/*
+ * 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.content.pm.cts.shortcutmanager;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertCallbackNotReceived;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertCallbackReceived;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.checkAssertSuccess;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.dumpsysShortcut;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.resetAll;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.setDefaultLauncher;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.waitUntil;
+
+import static org.mockito.Mockito.mock;
+
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
+import android.os.Handler;
+import android.os.Looper;
+import android.test.suitebuilder.annotation.SmallTest;
+
+/**
+ * Make sure switching between mPackageContext1..3 and mLauncherContext1..3 will work as intended.
+ */
+@SmallTest
+public class ShortcutManagerSpoofingTest extends ShortcutManagerCtsTestsBase {
+    /**
+     * Create shortcuts from different packages and make sure they're really different.
+     */
+    public void testSpoofingPublisher() {
+        runWithCaller(mPackageContext1, () -> {
+            ShortcutInfo s1 = makeShortcut("s1", "title1");
+            getManager().setDynamicShortcuts(list(s1));
+        });
+        runWithCaller(mPackageContext2, () -> {
+            ShortcutInfo s1 = makeShortcut("s1", "title2");
+            getManager().setDynamicShortcuts(list(s1));
+        });
+        runWithCaller(mPackageContext3, () -> {
+            ShortcutInfo s1 = makeShortcut("s1", "title3");
+            getManager().setDynamicShortcuts(list(s1));
+        });
+
+        runWithCaller(mPackageContext1, () -> {
+            assertWith(getManager().getDynamicShortcuts())
+                    .haveIds("s1")
+                    .forShortcutWithId("s1", s -> {
+                        assertEquals("title1", s.getShortLabel());
+                    });
+        });
+        runWithCaller(mPackageContext2, () -> {
+            assertWith(getManager().getDynamicShortcuts())
+                    .haveIds("s1")
+                    .forShortcutWithId("s1", s -> {
+                        assertEquals("title2", s.getShortLabel());
+                    });
+        });
+        runWithCaller(mPackageContext3, () -> {
+            assertWith(getManager().getDynamicShortcuts())
+                    .haveIds("s1")
+                    .forShortcutWithId("s1", s -> {
+                        assertEquals("title3", s.getShortLabel());
+                    });
+        });
+    }
+
+    public void testSpoofingLauncher() {
+        final LauncherApps.Callback c0_1 = mock(LauncherApps.Callback.class);
+        final LauncherApps.Callback c0_2 = mock(LauncherApps.Callback.class);
+        final LauncherApps.Callback c0_3 = mock(LauncherApps.Callback.class);
+        final Handler h = new Handler(Looper.getMainLooper());
+
+        runWithCaller(mLauncherContext1, () -> getLauncherApps().registerCallback(c0_1, h));
+        runWithCaller(mLauncherContext2, () -> getLauncherApps().registerCallback(c0_2, h));
+        runWithCaller(mLauncherContext3, () -> getLauncherApps().registerCallback(c0_3, h));
+
+        // Change the default launcher
+        setDefaultLauncher(getInstrumentation(), mLauncherContext2);
+
+        dumpsysShortcut(getInstrumentation());
+
+        runWithCaller(mLauncherContext1,
+                () -> assertFalse(getLauncherApps().hasShortcutHostPermission()));
+        runWithCaller(mLauncherContext2,
+                () -> assertTrue(getLauncherApps().hasShortcutHostPermission()));
+        runWithCaller(mLauncherContext3,
+                () -> assertFalse(getLauncherApps().hasShortcutHostPermission()));
+
+        // Call a publisher API and make sure only launcher2 gets it.
+
+        resetAll(list(c0_1, c0_2, c0_3));
+
+        runWithCaller(mPackageContext1, () -> {
+            ShortcutInfo s1 = makeShortcut("s1", "title1");
+            getManager().setDynamicShortcuts(list(s1));
+        });
+
+        // Because of the handlers, callback calls are not synchronous.
+        waitUntil("Launcher 2 didn't receive message", () ->
+                checkAssertSuccess(() ->
+                        assertCallbackReceived(c0_2, android.os.Process.myUserHandle(),
+                                mPackageContext1.getPackageName(), "s1")
+                )
+        );
+
+        assertCallbackNotReceived(c0_1);
+        assertCallbackNotReceived(c0_3);
+    }
+}
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerStartShortcutTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerStartShortcutTest.java
new file mode 100644
index 0000000..d08a242
--- /dev/null
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerStartShortcutTest.java
@@ -0,0 +1,22 @@
+/*
+ * 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.content.pm.cts.shortcutmanager;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+@SmallTest
+public class ShortcutManagerStartShortcutTest {
+}
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerThrottlingTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerThrottlingTest.java
new file mode 100644
index 0000000..78a55e1
--- /dev/null
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerThrottlingTest.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcutmanager;
+
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.resetThrottling;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.retryUntil;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.cts.shortcutmanager.common.Constants;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * The actual test is implemented in the CtsShortcutManagerThrottlingTest module.
+ * This class uses broadcast receivers to communicate with it, because if we just used an
+ * instrumentation test, the target process would never been throttled.
+ */
+@SmallTest
+public class ShortcutManagerThrottlingTest extends ShortcutManagerCtsTestsBase {
+    private void callTest(String method) {
+
+        final AtomicReference<Intent> ret = new AtomicReference<>();
+
+        // Register the reply receiver
+
+        // Use a random reply action every time.
+        final String replyAction = Constants.ACTION_THROTTLING_REPLY + sRandom.nextLong();
+        final IntentFilter filter = new IntentFilter(replyAction);
+
+        final BroadcastReceiver r = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                ret.set(intent);
+            }
+        };
+
+        getTestContext().registerReceiver(r, filter);
+
+        try {
+            // Send the request broadcast.
+
+            final Intent i = new Intent(Constants.ACTION_THROTTLING_TEST);
+            i.putExtra(Constants.EXTRA_METHOD, method);
+            i.putExtra(Constants.EXTRA_REPLY_ACTION, replyAction);
+            i.setComponent(ComponentName.unflattenFromString(
+                    "android.content.pm.cts.shortcutmanager.throttling/"
+                            + ".ShortcutManagerThrottlingTestReceiver"
+                    ));
+            getTestContext().sendBroadcast(i);
+
+            // Wait for the response.
+            retryUntil(() -> ret.get() != null, "Didn't receiver result broadcast");
+
+            if (ret.get().getExtras().getBoolean("success")) {
+                return;
+            }
+            fail(ret.get().getExtras().getString("error"));
+        } finally {
+            getTestContext().unregisterReceiver(r);
+        }
+    }
+
+    public void testThrottling() throws InterruptedException {
+        resetThrottling(getInstrumentation());
+
+        callTest(Constants.TEST_SET_DYNAMIC_SHORTCUTS);
+
+        // --------------------------------------
+        resetThrottling(getInstrumentation());
+
+        callTest(Constants.TEST_ADD_DYNAMIC_SHORTCUTS);
+
+        // --------------------------------------
+        resetThrottling(getInstrumentation());
+
+        callTest(Constants.TEST_UPDATE_SHORTCUTS);
+
+        // --------------------------------------
+        resetThrottling(getInstrumentation());
+
+        callTest(Constants.TEST_BG_SERVICE_THROTTLED);
+
+        // --------------------------------------
+        resetThrottling(getInstrumentation());
+
+        callTest(Constants.TEST_ACTIVITY_UNTHROTTLED);
+
+        // --------------------------------------
+        resetThrottling(getInstrumentation());
+
+        callTest(Constants.TEST_FG_SERVICE_UNTHROTTLED);
+    }
+}
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerUsageTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerUsageTest.java
new file mode 100644
index 0000000..eac7666
--- /dev/null
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerUsageTest.java
@@ -0,0 +1,135 @@
+/*
+ * 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.content.pm.cts.shortcutmanager;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.appOps;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.retryUntil;
+
+import android.app.AppOpsManager;
+import android.app.usage.UsageEvents;
+import android.app.usage.UsageEvents.Event;
+import android.app.usage.UsageStatsManager;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.text.format.Time;
+
+@SmallTest
+public class ShortcutManagerUsageTest extends ShortcutManagerCtsTestsBase {
+    private static final String APPOPS_SET_SHELL_COMMAND = "appops set {0} " +
+            AppOpsManager.OPSTR_GET_USAGE_STATS + " {1}";
+
+    // We need some allowance due to b/30415390.
+    private static long USAGE_STATS_RANGE_ALLOWANCE = 2 * 1000;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        appOps(getInstrumentation(), getTestContext().getPackageName(),
+                AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        appOps(getInstrumentation(), getTestContext().getPackageName(),
+                AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
+
+        super.tearDown();
+    }
+
+    private static String generateRandomId(String signature) {
+        Time tobj = new Time();
+        tobj.set(System.currentTimeMillis());
+        return tobj.format("%Y-%m-%d %H:%M:%S") + "." + signature + "."
+                + sRandom.nextLong();
+    }
+
+    private boolean hasEvent(UsageEvents events, String packageName, String id) {
+        final Event e = new Event();
+        while (events.hasNextEvent()) {
+            if (!events.getNextEvent(e)) {
+                break;
+            }
+            if (e.getEventType() == Event.SHORTCUT_INVOCATION
+                    && packageName.equals(e.getPackageName())
+                    && id.equals(e.getShortcutId())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public void testReportShortcutUsed() throws InterruptedException {
+
+        runWithCaller(mPackageContext1, () -> {
+            enableManifestActivity("Launcher_manifest_2", true);
+
+            retryUntil(() -> getManager().getManifestShortcuts().size() > 0,
+                    "Manifest shortcuts didn't show up");
+        });
+
+        final String id1 = generateRandomId("id1");
+        final String id2 = generateRandomId("id2");
+        final String id3 = generateRandomId("id3");
+
+        final String idManifest = "ms21";
+        final String idNonexistance = "nonexistence";
+
+        runWithCaller(mPackageContext1, () -> {
+            assertTrue(getManager().setDynamicShortcuts(list(
+                    makeShortcut(id1),
+                    makeShortcut(id2)
+            )));
+        });
+        runWithCaller(mPackageContext2, () -> {
+            assertTrue(getManager().setDynamicShortcuts(list(
+                    makeShortcut(id1),
+                    makeShortcut(id3)
+            )));
+        });
+
+        // Report usage.
+        final long start = System.currentTimeMillis() - USAGE_STATS_RANGE_ALLOWANCE;
+
+        runWithCaller(mPackageContext1, () -> getManager().reportShortcutUsed(id1));
+        runWithCaller(mPackageContext1, () -> getManager().reportShortcutUsed(idManifest));
+        runWithCaller(mPackageContext1, () -> getManager().reportShortcutUsed(idNonexistance));
+
+        runWithCaller(mPackageContext2, () -> getManager().reportShortcutUsed(id3));
+
+        final long end = System.currentTimeMillis() + USAGE_STATS_RANGE_ALLOWANCE;
+
+        // Check the log.
+        final UsageStatsManager usm = getTestContext().getSystemService(UsageStatsManager.class);
+
+        // Note it's a bit silly, but because events are populated asynchronously,
+        // we need to wait until the last one is populated.
+
+        retryUntil(() -> hasEvent(usm.queryEvents(start, end),
+                mPackageContext2.getPackageName(), id3), "Events weren't populated");
+
+        assertTrue(hasEvent(usm.queryEvents(start, end),
+                mPackageContext1.getPackageName(), id1));
+
+        assertTrue(hasEvent(usm.queryEvents(start, end),
+                mPackageContext1.getPackageName(), idManifest));
+        assertFalse(hasEvent(usm.queryEvents(start, end),
+                mPackageContext1.getPackageName(), idNonexistance));
+
+        assertTrue(hasEvent(usm.queryEvents(start, end),
+                mPackageContext2.getPackageName(), id3));
+    }
+}
diff --git a/tests/tests/shortcutmanager/throttling/Android.mk b/tests/tests/shortcutmanager/throttling/Android.mk
new file mode 100644
index 0000000..59d1f57
--- /dev/null
+++ b/tests/tests/shortcutmanager/throttling/Android.mk
@@ -0,0 +1,42 @@
+# 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)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_PACKAGE_NAME := CtsShortcutManagerThrottlingTest
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+    $(call all-java-files-under, ../common/src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    android-support-v4 \
+    mockito-target \
+    ctsdeviceutil \
+    ctstestrunner \
+    ub-uiautomator \
+    ShortcutManagerTestUtils
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/shortcutmanager/throttling/AndroidManifest.xml b/tests/tests/shortcutmanager/throttling/AndroidManifest.xml
new file mode 100644
index 0000000..305f8e1
--- /dev/null
+++ b/tests/tests/shortcutmanager/throttling/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?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.content.pm.cts.shortcutmanager.throttling">
+
+    <application>
+        <receiver
+            android:name="ShortcutManagerThrottlingTestReceiver"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.content.pm.cts.shortcutmanager.ACTION_THROTTLING_TEST" />
+            </intent-filter>
+        </receiver>
+
+        <activity android:name=".MyActivity" />
+
+        <service android:name=".BgService" />
+
+        <service android:name=".FgService" />
+    </application>
+</manifest>
+
diff --git a/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/BgService.java b/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/BgService.java
new file mode 100644
index 0000000..fbf8079
--- /dev/null
+++ b/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/BgService.java
@@ -0,0 +1,63 @@
+/*
+ * 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.content.pm.cts.shortcutmanager.throttling;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ShortcutManager;
+import android.content.pm.cts.shortcutmanager.common.Constants;
+import android.os.IBinder;
+import android.util.Log;
+
+/**
+ * Make sure that when only a bg service is running, shortcut manager calls are throttled.
+ */
+public class BgService extends Service {
+    public static void start(Context context, String replyAction) {
+        final Intent i =
+                new Intent().setComponent(new ComponentName(context, BgService.class))
+                        .putExtra(Constants.EXTRA_REPLY_ACTION, replyAction);
+        context.startService(i);
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        final Context context = getApplicationContext();
+        final ShortcutManager manager = context.getSystemService(ShortcutManager.class);
+
+        final String replyAction = intent.getStringExtra(Constants.EXTRA_REPLY_ACTION);
+
+        Log.i(ThrottledTests.TAG, Constants.TEST_BG_SERVICE_THROTTLED);
+
+        ReplyUtil.runTestAndReply(context, replyAction, () -> {
+            ThrottledTests.assertThrottled(
+                    context, () -> manager.setDynamicShortcuts(list()));
+        });
+
+        stopSelf();
+
+        return Service.START_NOT_STICKY;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+}
diff --git a/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/FgService.java b/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/FgService.java
new file mode 100644
index 0000000..86f9dc7
--- /dev/null
+++ b/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/FgService.java
@@ -0,0 +1,68 @@
+/*
+ * 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.content.pm.cts.shortcutmanager.throttling;
+
+import android.app.Notification;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.cts.shortcutmanager.common.Constants;
+import android.os.IBinder;
+import android.util.Log;
+
+/**
+ * Make sure that when a fg service is running, shortcut manager calls are not throttled.
+ */
+public class FgService extends Service {
+    public static void start(Context context, String replyAction) {
+        final Intent i =
+                new Intent().setComponent(new ComponentName(context, FgService.class))
+                        .putExtra(Constants.EXTRA_REPLY_ACTION, replyAction);
+        context.startService(i);
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+
+        // Start as foreground.
+        Notification notification = new Notification.Builder(getApplicationContext())
+                .setContentTitle("FgService")
+                .setSmallIcon(android.R.drawable.ic_popup_sync)
+                .build();
+        startForeground(1, notification);
+
+        final String replyAction = intent.getStringExtra(Constants.EXTRA_REPLY_ACTION);
+
+        Log.i(ThrottledTests.TAG, Constants.TEST_FG_SERVICE_UNTHROTTLED);
+
+        // Actual test.
+        ReplyUtil.runTestAndReply(this, replyAction, () -> {
+            ThrottledTests.assertCallNotThrottled(this);
+        });
+
+        // Stop self.
+        stopForeground(true);
+        stopSelf();
+
+        return Service.START_NOT_STICKY;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+}
diff --git a/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/MyActivity.java b/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/MyActivity.java
new file mode 100644
index 0000000..d04a890
--- /dev/null
+++ b/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/MyActivity.java
@@ -0,0 +1,41 @@
+/*
+ * 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.content.pm.cts.shortcutmanager.throttling;
+
+import android.app.Activity;
+import android.content.pm.cts.shortcutmanager.common.Constants;
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * Make sure when it's running shortcut manger calls are not throttled.
+ */
+public class MyActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        final String replyAction = getIntent().getStringExtra(Constants.EXTRA_REPLY_ACTION);
+
+        Log.i(ThrottledTests.TAG, Constants.TEST_ACTIVITY_UNTHROTTLED);
+
+        ReplyUtil.runTestAndReply(this, replyAction, () -> {
+            ThrottledTests.assertCallNotThrottled(this);
+        });
+
+        finish();
+    }
+}
diff --git a/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/ReplyUtil.java b/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/ReplyUtil.java
new file mode 100644
index 0000000..1b0471c
--- /dev/null
+++ b/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/ReplyUtil.java
@@ -0,0 +1,54 @@
+/*
+ * 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.content.pm.cts.shortcutmanager.throttling;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+public class ReplyUtil {
+    private ReplyUtil() {
+    }
+
+    public static void runTestAndReply(Context context, String replyAction, Runnable test) {
+        try {
+            test.run();
+
+            sendReply(context, replyAction, null);
+        } catch (Throwable e) {
+            String error = "Test failed: " + e.getMessage() + "\n" + Log.getStackTraceString(e);
+            sendReply(context, replyAction, error);
+        }
+    }
+
+    public static void sendReply(Context context, String replyAction,
+            String failureMessageOrNullForSuccess) {
+        // Create the reply bundle.
+        final Bundle ret = new Bundle();
+        if (failureMessageOrNullForSuccess == null) {
+            ret.putBoolean("success", true);
+        } else {
+            ret.putString("error", failureMessageOrNullForSuccess);
+        }
+
+        // Send reply
+        final Intent reply = new Intent(replyAction);
+        reply.putExtras(ret);
+
+        context.sendBroadcast(reply);
+    }
+}
diff --git a/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/ShortcutManagerThrottlingTestReceiver.java b/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/ShortcutManagerThrottlingTestReceiver.java
new file mode 100644
index 0000000..2cbaf20
--- /dev/null
+++ b/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/ShortcutManagerThrottlingTestReceiver.java
@@ -0,0 +1,138 @@
+/*
+ * 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.content.pm.cts.shortcutmanager.throttling;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ShortcutManager;
+import android.content.pm.cts.shortcutmanager.common.Constants;
+import android.util.Log;
+
+
+/**
+ * Throttling test case.
+ *
+ * If we run it as a regular instrumentation test, the process would always considered to be in the
+ * foreground and will never be throttled, so we use a broadcast to communicate from the
+ * main test apk.
+ */
+public class ShortcutManagerThrottlingTestReceiver extends BroadcastReceiver {
+    private ShortcutManager mManager;
+
+    public ShortcutManager getManager(Context context) {
+        if (mManager == null) {
+            mManager = context.getSystemService(ShortcutManager.class);
+        }
+        return mManager;
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (Constants.ACTION_THROTTLING_TEST.equals(intent.getAction())) {
+            final String replyAction = intent.getStringExtra(Constants.EXTRA_REPLY_ACTION);
+            final String method = intent.getStringExtra(Constants.EXTRA_METHOD);
+            switch (method) {
+                case Constants.TEST_SET_DYNAMIC_SHORTCUTS:
+                    testSetDynamicShortcuts(context, replyAction);
+                    break;
+                case Constants.TEST_ADD_DYNAMIC_SHORTCUTS:
+                    testAddDynamicShortcuts(context, replyAction);
+                    break;
+                case Constants.TEST_UPDATE_SHORTCUTS:
+                    testUpdateShortcuts(context, replyAction);
+                    break;
+
+                case Constants.TEST_BG_SERVICE_THROTTLED:
+                    testBgServiceThrottled(context, replyAction);
+                    break;
+
+                case Constants.TEST_ACTIVITY_UNTHROTTLED:
+                    testActivityUnthrottled(context, replyAction);
+                    break;
+                case Constants.TEST_FG_SERVICE_UNTHROTTLED:
+                    testFgServiceUnthrottled(context, replyAction);
+                    break;
+
+                default:
+                    ReplyUtil.sendReply(context, replyAction, "Unknown test: " + method);
+                    break;
+            }
+        }
+    }
+
+    public void testSetDynamicShortcuts(Context context, String replyAction) {
+        Log.i(ThrottledTests.TAG, Constants.TEST_SET_DYNAMIC_SHORTCUTS);
+
+        ReplyUtil.runTestAndReply(context, replyAction, () -> {
+            ThrottledTests.assertThrottled(
+                    context, () -> getManager(context).setDynamicShortcuts(list()));
+        });
+    }
+
+    public void testAddDynamicShortcuts(Context context, String replyAction) {
+        Log.i(ThrottledTests.TAG, Constants.TEST_ADD_DYNAMIC_SHORTCUTS);
+
+        ReplyUtil.runTestAndReply(context, replyAction, () -> {
+            ThrottledTests.assertThrottled(
+                    context, () -> getManager(context).addDynamicShortcuts(list()));
+        });
+    }
+
+    public void testUpdateShortcuts(Context context, String replyAction) {
+        Log.i(ThrottledTests.TAG, Constants.TEST_UPDATE_SHORTCUTS);
+
+        ReplyUtil.runTestAndReply(context, replyAction, () -> {
+            ThrottledTests.assertThrottled(
+                    context, () -> getManager(context).updateShortcuts(list()));
+        });
+    }
+
+    public void testBgServiceThrottled(Context context, String replyAction) {
+        ReplyUtil.runTestAndReply(context, replyAction, () -> {
+
+            BgService.start(context, replyAction);
+        });
+    }
+
+    public void testActivityUnthrottled(Context context, String replyAction) {
+        ReplyUtil.runTestAndReply(context, replyAction, () -> {
+
+            // First make sure the self is throttled.
+            ThrottledTests.ensureThrottled(context);
+
+            // Run the activity that runs the actual test.
+            final Intent i =
+                    new Intent().setComponent(new ComponentName(context, MyActivity.class))
+                    .putExtra(Constants.EXTRA_REPLY_ACTION, replyAction)
+                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+            context.startActivity(i);
+        });
+    }
+
+    public void testFgServiceUnthrottled(Context context, String replyAction) {
+        ReplyUtil.runTestAndReply(context, replyAction, () -> {
+
+            // First make sure the self is throttled.
+            ThrottledTests.ensureThrottled(context);
+
+            FgService.start(context, replyAction);
+        });
+    }
+}
diff --git a/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/ThrottledTests.java b/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/ThrottledTests.java
new file mode 100644
index 0000000..5053e87
--- /dev/null
+++ b/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/ThrottledTests.java
@@ -0,0 +1,75 @@
+/*
+ * 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.content.pm.cts.shortcutmanager.throttling;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.retryUntil;
+
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertFalse;
+
+import android.content.Context;
+import android.content.pm.ShortcutManager;
+
+import java.util.function.BooleanSupplier;
+
+public class ThrottledTests {
+    public static String TAG = "ShortcutThrottledTests";
+
+    private ThrottledTests() {
+    }
+
+    public static void assertThrottled(Context context, BooleanSupplier apiCall) {
+        final ShortcutManager manager = context.getSystemService(ShortcutManager.class);
+
+        assertFalse("Throttling must be reset here", manager.isRateLimitingActive());
+
+        assertTrue("First call should succeed", apiCall.getAsBoolean());
+
+        // App can make 10 API calls between the interval, but there's a chance that the throttling
+        // gets reset within this loop, so we make 20 calls.
+        boolean throttled = false;
+        for (int i = 0; i < 19; i++) {
+            if (!apiCall.getAsBoolean()) {
+                throttled = true;
+                break;
+            }
+        }
+        assertTrue("API call not throttled", throttled);
+    }
+
+    /**
+     * Call shortcut manager APIs until throttled.
+     */
+    public static void ensureThrottled(Context context) {
+        final ShortcutManager manager = context.getSystemService(ShortcutManager.class);
+
+        retryUntil(() -> !manager.setDynamicShortcuts(list()), "Not throttled.");
+    }
+
+    public static void assertCallNotThrottled(Context context) {
+        final ShortcutManager manager = context.getSystemService(ShortcutManager.class);
+
+        assertFalse(manager.isRateLimitingActive());
+
+        for (int i = 0; i < 20; i++) {
+            assertTrue(manager.setDynamicShortcuts(list()));
+            assertTrue(manager.addDynamicShortcuts(list()));
+            assertTrue(manager.updateShortcuts(list()));
+        }
+    }
+}
diff --git a/tests/tests/telecom/AndroidManifest.xml b/tests/tests/telecom/AndroidManifest.xml
index 46fdf09..7231954 100644
--- a/tests/tests/telecom/AndroidManifest.xml
+++ b/tests/tests/telecom/AndroidManifest.xml
@@ -46,6 +46,7 @@
                 <action android:name="android.telecom.InCallService"/>
             </intent-filter>
             <meta-data android:name="android.telecom.IN_CALL_SERVICE_UI" android:value="true" />
+            <meta-data android:name="android.telecom.INCLUDE_EXTERNAL_CALLS" android:value="true" />
         </service>
 
         <service android:name="android.telecom.cts.MockCallScreeningService"
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..6a33860 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();
+
+        assertTrue(extras == null || extras.isEmpty());
+    }
+
+    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..1fc65c1 100644
--- a/tests/tests/telecom/src/android/telecom/cts/ConnectionTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/ConnectionTest.java
@@ -69,6 +69,10 @@
         waitForStateChange(lock);
         assertEquals(Connection.STATE_HOLDING, connection.getState());
 
+        connection.setPulling();
+        waitForStateChange(lock);
+        assertEquals(Connection.STATE_PULLING_CALL, connection.getState());
+
         connection.setDisconnected(
                 new DisconnectCause(DisconnectCause.LOCAL, "Test call"));
         waitForStateChange(lock);
@@ -164,6 +168,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 +237,109 @@
         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(retrieved.containsKey("test-extra-key"));
+    }
+
+    /**
+     * Basic local test of removing extra keys via {@link Connection#removeExtras(String...)}.
+     *
+     * Extended end-to-end passing of extras is verified in
+     * {@link CallDetailsTest#testConnectionPutExtras()} and
+     * @link CallDetailsTest#testConnectionRemoveExtras()}.
+     */
+    public void testRemoveExtrasVariable() {
+        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);
+        extras.putBoolean("test-extra-key2", true);
+        connection.putExtras(extras);
+        connection.removeExtras("test-extra-key", "test-extra-key2");
+
+        final Bundle retrieved = connection.getExtras();
+        assertNotNull(retrieved);
+        assertFalse(retrieved.containsKey("test-extra-key"));
+        assertFalse(retrieved.containsKey("test-extra-key2"));
+    }
+
+    /**
+     * 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 +418,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/text/src/android/text/cts/SpannableStringTest.java b/tests/tests/text/src/android/text/cts/SpannableStringTest.java
index e46a104..1eca046 100644
--- a/tests/tests/text/src/android/text/cts/SpannableStringTest.java
+++ b/tests/tests/text/src/android/text/cts/SpannableStringTest.java
@@ -184,4 +184,16 @@
         }
     }
 
+    @SmallTest
+    public void testCopyGrowable() {
+        SpannableString first = new SpannableString("t\nest data");
+        final int N_SPANS = 127;
+        for (int i = 0; i < N_SPANS; i++) {
+            first.setSpan(new QuoteSpan(), 0, 2, Spanned.SPAN_PARAGRAPH);
+        }
+        SpannableString second = new SpannableString(first.subSequence(0, first.length() - 1));
+        second.setSpan(new LocaleSpan(Locale.US), 2, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        Object[] secondSpans = second.getSpans(0, second.length(), Object.class);
+        assertEquals(secondSpans.length, N_SPANS + 1);
+    }
 }
diff --git a/tests/tests/uirendering/AndroidManifest.xml b/tests/tests/uirendering/AndroidManifest.xml
index 24cdd8a..fcd38ae 100644
--- a/tests/tests/uirendering/AndroidManifest.xml
+++ b/tests/tests/uirendering/AndroidManifest.xml
@@ -21,9 +21,15 @@
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <application>
-<!--            android:theme="@style/WhiteBackgroundTheme"> -->
+        <!--
+         * Some tests (e.g. ShadowTests#testShadowLayout) may have different results depending on
+         * the position on screen, so current implementation of verifier assumes that rendered image
+         * is centered. Therefore activity made non-resizable to work correctly on devices that
+         * start up in multi-window mode.
+         -->
         <activity android:name="android.uirendering.cts.testinfrastructure.DrawActivity"
-                android:theme="@style/WhiteBackgroundTheme"></activity>
+                  android:theme="@style/WhiteBackgroundTheme"
+                  android:resizeableActivity="false" />
         <uses-library android:name="android.test.runner" />
     </application>
 
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..6a23e02
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/CapturedActivity.java
@@ -0,0 +1,196 @@
+/*
+ * 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.pm.PackageManager;
+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);
+
+        mOnWatch = getPackageManager().hasSystemFeature(PackageManager.FEATURE_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/tests/tests/widget/src/android/widget/cts/PopupWindowTest.java b/tests/tests/widget/src/android/widget/cts/PopupWindowTest.java
index 96b37d3..47efffc 100644
--- a/tests/tests/widget/src/android/widget/cts/PopupWindowTest.java
+++ b/tests/tests/widget/src/android/widget/cts/PopupWindowTest.java
@@ -570,6 +570,7 @@
     public void testShowAtLocation() {
         int[] popupContentViewInWindowXY = new int[2];
         int[] popupContentViewOnScreenXY = new int[2];
+        Rect containingRect = new Rect();
 
         mPopupWindow = createPopupWindow(createPopupContent(50, 50));
         // Do not attach within the decor; we will be measuring location
@@ -591,10 +592,14 @@
         assertTrue(mPopupWindow.isShowing());
         mPopupWindow.getContentView().getLocationInWindow(popupContentViewInWindowXY);
         mPopupWindow.getContentView().getLocationOnScreen(popupContentViewOnScreenXY);
+        upperAnchor.getWindowDisplayFrame(containingRect);
+
         assertTrue(popupContentViewInWindowXY[0] >= 0);
         assertTrue(popupContentViewInWindowXY[1] >= 0);
-        assertEquals(popupContentViewInWindowXY[0] + xOff, popupContentViewOnScreenXY[0]);
-        assertEquals(popupContentViewInWindowXY[1] + yOff, popupContentViewOnScreenXY[1]);
+        assertEquals(containingRect.left + popupContentViewInWindowXY[0] + xOff,
+                popupContentViewOnScreenXY[0]);
+        assertEquals(containingRect.top + popupContentViewInWindowXY[1] + yOff,
+                popupContentViewOnScreenXY[1]);
 
         dismissPopup();
     }
@@ -804,6 +809,7 @@
         int[] fstXY = new int[2];
         int[] sndXY = new int[2];
         int[] viewInWindowXY = new int[2];
+        Rect containingRect = new Rect();
 
         mInstrumentation.runOnMainSync(() -> {
             mPopupWindow = createPopupWindow(createPopupContent(50, 50));
@@ -820,6 +826,8 @@
 
         showPopup();
         mPopupWindow.getContentView().getLocationInWindow(viewInWindowXY);
+        final View containerView = mActivity.findViewById(R.id.main_container);
+        containerView.getWindowDisplayFrame(containingRect);
 
         // update if it is not shown
         mInstrumentation.runOnMainSync(() -> mPopupWindow.update(20, 50, 50, 50));
@@ -830,8 +838,8 @@
         assertEquals(50, mPopupWindow.getHeight());
 
         mPopupWindow.getContentView().getLocationOnScreen(fstXY);
-        assertEquals(viewInWindowXY[0] + 20, fstXY[0]);
-        assertEquals(viewInWindowXY[1] + 50, fstXY[1]);
+        assertEquals(containingRect.left + viewInWindowXY[0] + 20, fstXY[0]);
+        assertEquals(containingRect.top + viewInWindowXY[1] + 50, fstXY[1]);
 
         // ignore if width or height is -1
         mInstrumentation.runOnMainSync(() -> mPopupWindow.update(4, 0, -1, -1, true));
@@ -842,8 +850,8 @@
         assertEquals(50, mPopupWindow.getHeight());
 
         mPopupWindow.getContentView().getLocationOnScreen(sndXY);
-        assertEquals(viewInWindowXY[0] + 4, sndXY[0]);
-        assertEquals(viewInWindowXY[1], sndXY[1]);
+        assertEquals(containingRect.left + viewInWindowXY[0] + 4, sndXY[0]);
+        assertEquals(containingRect.top + viewInWindowXY[1], sndXY[1]);
 
         dismissPopup();
     }
diff --git a/tests/tests/widget/src/android/widget/cts/SeekBarTest.java b/tests/tests/widget/src/android/widget/cts/SeekBarTest.java
index b9ad733..9182bcc 100644
--- a/tests/tests/widget/src/android/widget/cts/SeekBarTest.java
+++ b/tests/tests/widget/src/android/widget/cts/SeekBarTest.java
@@ -65,7 +65,7 @@
         long downTime = SystemClock.uptimeMillis();
         long eventTime = SystemClock.uptimeMillis();
         int seekBarXY[] = new int[2];
-        mSeekBar.getLocationInWindow(seekBarXY);
+        mSeekBar.getLocationOnScreen(seekBarXY);
         MotionEvent event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_DOWN,
                 seekBarXY[0], seekBarXY[1], 0);
         mInstrumentation.sendPointerSync(event);
diff --git a/tests/vr/Android.mk b/tests/vr/Android.mk
index 04644a0..92c99b1 100644
--- a/tests/vr/Android.mk
+++ b/tests/vr/Android.mk
@@ -29,9 +29,9 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
 
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES := $(call all-java-files-under, src) ../../apps/CtsVerifier/src/com/android/cts/verifier/vr/MockVrListenerService.java
 
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := test_current
 
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts
diff --git a/tests/vr/AndroidManifest.xml b/tests/vr/AndroidManifest.xml
index 877d846..1271665 100644
--- a/tests/vr/AndroidManifest.xml
+++ b/tests/vr/AndroidManifest.xml
@@ -19,6 +19,7 @@
     android:versionName="1.0" >
 
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
     <uses-sdk android:minSdkVersion="14" />
     <uses-feature android:glEsVersion="0x00020000"/>
     <instrumentation
@@ -33,6 +34,16 @@
         android:label="@string/app_name"
         android:hardwareAccelerated="false" >
 
+	<service android:name="com.android.cts.verifier.vr.MockVrListenerService"
+            android:exported="true"
+            android:enabled="true"
+            android:label="@string/vr_service_name"
+            android:permission="android.permission.BIND_VR_LISTENER_SERVICE">
+            <intent-filter>
+                <action android:name="android.service.vr.VrListenerService" />
+            </intent-filter>
+        </service>
+
          <activity
             android:label="@string/app_name"
             android:name="android.vr.cts.OpenGLESActivity">
diff --git a/tests/vr/res/values/strings.xml b/tests/vr/res/values/strings.xml
index e87ad4a..0b0b0c1 100644
--- a/tests/vr/res/values/strings.xml
+++ b/tests/vr/res/values/strings.xml
@@ -15,5 +15,5 @@
 -->
 <resources>
     <string name="app_name">CtsVrTestCases</string>
-
+    <string name="vr_service_name">VR Listener for CTS</string>
 </resources>
diff --git a/tests/vr/src/android/vr/cts/VrSetFIFOThreadTest.java b/tests/vr/src/android/vr/cts/VrSetFIFOThreadTest.java
new file mode 100644
index 0000000..85efc8a
--- /dev/null
+++ b/tests/vr/src/android/vr/cts/VrSetFIFOThreadTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.vr.cts;
+
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Process;
+import android.test.ActivityInstrumentationTestCase2;
+import android.content.ComponentName;
+import android.util.Log;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.os.RemoteException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import android.provider.Settings;
+import com.android.cts.verifier.vr.MockVrListenerService;
+
+public class VrSetFIFOThreadTest extends ActivityInstrumentationTestCase2<OpenGLESActivity> {
+    private OpenGLESActivity mActivity;
+    private ActivityManager mActivityManager;
+    private Context mContext;
+    private static final int SCHED_OTHER = 0;
+    private static final int SCHED_FIFO = 1;
+    private static final int SCHED_RESET_ON_FORK = 0x40000000;
+    public static final String ENABLED_VR_LISTENERS = "enabled_vr_listeners";
+    private static final String TAG = "VrSetFIFOThreadTest";
+
+    public VrSetFIFOThreadTest() {
+        super(OpenGLESActivity.class);
+    }
+
+    private void setIntent(int viewIndex, int createProtected,
+        int priorityAttribute, int mutableAttribute) {
+        Intent intent = new Intent();
+        intent.putExtra(OpenGLESActivity.EXTRA_VIEW_INDEX, viewIndex);
+        intent.putExtra(OpenGLESActivity.EXTRA_PROTECTED, createProtected);
+        intent.putExtra(OpenGLESActivity.EXTRA_PRIORITY, priorityAttribute);
+        intent.putExtra(OpenGLESActivity.EXTRA_MUTABLE, mutableAttribute);
+        setActivityIntent(intent);
+    }
+
+    public void testSetVrThreadAPISuccess() throws Throwable {
+        mContext = getInstrumentation().getTargetContext();
+        setIntent(1, 1, 0, 0);
+        ComponentName requestedComponent = new ComponentName(mContext, MockVrListenerService.class);
+        String old_vr_listener = Settings.Secure.getString(mContext.getContentResolver(), ENABLED_VR_LISTENERS);
+        Settings.Secure.putString(mContext.getContentResolver(),
+            ENABLED_VR_LISTENERS,
+            requestedComponent.flattenToString());
+        mActivity = getActivity();
+        assertTrue(mActivity.waitForFrameDrawn());
+
+        if (mActivity.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE)) {
+            int vr_thread = 0, policy = 0;
+            mActivity.setVrModeEnabled(true, requestedComponent);
+            vr_thread = Process.myTid();
+            mActivityManager =
+                  (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+            mActivityManager.setVrThread(vr_thread);
+            policy = (int) Process.getThreadScheduler(vr_thread);
+            Log.e(TAG, "scheduling policy: " + policy);
+            assertEquals((SCHED_FIFO | SCHED_RESET_ON_FORK), policy);
+        }
+        Settings.Secure.putString(mContext.getContentResolver(),
+            ENABLED_VR_LISTENERS, old_vr_listener);
+    }
+
+    public void testSetVrThreadAPIFailure() throws Throwable {
+        mContext = getInstrumentation().getTargetContext();
+        setIntent(1, 1, 0, 0);
+        ComponentName requestedComponent = new ComponentName(mContext, MockVrListenerService.class);
+        String old_vr_listener = Settings.Secure.getString(mContext.getContentResolver(), ENABLED_VR_LISTENERS);
+        Settings.Secure.putString(mContext.getContentResolver(),
+            ENABLED_VR_LISTENERS,
+            requestedComponent.flattenToString());
+        mActivity = getActivity();
+        assertTrue(mActivity.waitForFrameDrawn());
+        if (mActivity.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE)) {
+            int vr_thread = 0, policy = 0;
+            mActivity.setVrModeEnabled(false, requestedComponent);
+            vr_thread = Process.myTid();
+            mActivityManager =
+                  (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+            mActivityManager.setVrThread(vr_thread);
+            policy = (int) Process.getThreadScheduler(vr_thread);
+            Log.e(TAG, "scheduling policy: " + policy);
+            assertEquals(SCHED_OTHER, policy);
+        }
+        Settings.Secure.putString(mContext.getContentResolver(),
+            ENABLED_VR_LISTENERS, old_vr_listener);
+    }
+}
diff --git a/tools/cts-device-info/src/com/android/cts/deviceinfo/VulkanDeviceInfo.java b/tools/cts-device-info/src/com/android/cts/deviceinfo/VulkanDeviceInfo.java
index ddacc28..fc99d75 100644
--- a/tools/cts-device-info/src/com/android/cts/deviceinfo/VulkanDeviceInfo.java
+++ b/tools/cts-device-info/src/com/android/cts/deviceinfo/VulkanDeviceInfo.java
@@ -47,16 +47,15 @@
  * - DeviceInfoStore doesn't allow arrays of non-uniform or non-primitive types. VkJSON stores
  *   format capabilities as an array of formats, where each format is an array containing a number
  *   (the format enum value) and an object (the format properties). Since DeviceInfoStore doesn't
- *   allow array-of-array, we instead store formats as an object, with format enum values as keys
- *   and the format property objects as values. This is arguably a more natural and useful
- *   representation anyway. So instead of
+ *   allow array-of-array, we instead store formats as an array of uniform structs, So instead of
  *       [[3, {
  *           "linearTilingFeatures": 0,
  *           "optimalTilingFeatures": 5121,
  *           "bufferFeatures": 0
  *       }]]
  *   the format with enum value "3" will be represented as
- *       "format_3": {
+ *       {
+ *           "id": 3,
  *           "linear_tiling_features": 0,
  *           "optimal_tiling_features": 5121,
  *           "buffer_features": 0
@@ -568,19 +567,22 @@
                 emitExtensions(store, device);
 
                 JSONArray formats = device.getJSONArray(KEY_FORMATS);
-                store.startGroup(convertName(KEY_FORMATS));
+                // Note: Earlier code used field named 'formats' with different data structure.
+                // In order to have the mix of old and new data, we cannot reuse that name.
+                store.startArray("supported_formats");
                 for (int formatIdx = 0; formatIdx < formats.length(); formatIdx++) {
                     JSONArray formatPair = formats.getJSONArray(formatIdx);
                     JSONObject formatProperties = formatPair.getJSONObject(1);
-                    store.startGroup("format_" + formatPair.getInt(0));
+                    store.startGroup();
                     {
+                        store.addResult("format", (long)formatPair.getInt(0));
                         emitLong(store, formatProperties, KEY_LINEAR_TILING_FEATURES);
                         emitLong(store, formatProperties, KEY_OPTIMAL_TILING_FEATURES);
                         emitLong(store, formatProperties, KEY_BUFFER_FEATURES);
                     }
                     store.endGroup();
                 }
-                store.endGroup();
+                store.endArray();
             }
             store.endGroup();
         }
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/cts-tradefed/res/config/cts-known-failures.xml b/tools/cts-tradefed/res/config/cts-known-failures.xml
index e1eeba6..e7b08cb 100644
--- a/tools/cts-tradefed/res/config/cts-known-failures.xml
+++ b/tools/cts-tradefed/res/config/cts-known-failures.xml
@@ -23,9 +23,6 @@
     <option name="compatibility:exclude-filter" value="CtsAccessibilityServiceTestCases android.accessibilityservice.cts.AccessibilityTextTraversalTest#testActionNextAndPreviousAtGranularityPageOverText" />
     <option name="compatibility:exclude-filter" value="CtsAccessibilityServiceTestCases android.accessibilityservice.cts.AccessibilityTextTraversalTest#testActionNextAndPreviousAtGranularityPageOverTextExtend" />
 
-    <!-- b/17508787 -->
-    <option name="compatibility:exclude-filter" value="CtsAdminTestCases android.admin.cts.DevicePolicyManagerTest#testUninstallAllUserCaCerts_failIfNotProfileOwner" />
-
     <!-- b/23776083 -->
     <option name="compatibility:exclude-filter" value="CtsAlarmClockTestCases" />
 
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});