Add new test type to StagefrightTest
am: 6724527ab5

Change-Id: I9cef7005cdf4f42b9636683c6ae4c579256c3c93
diff --git a/CtsCoverage.mk b/CtsCoverage.mk
index 8cc8a96..c4536f6 100644
--- a/CtsCoverage.mk
+++ b/CtsCoverage.mk
@@ -36,10 +36,10 @@
 
 cts_api_coverage_dependencies := $(cts_api_coverage_exe) $(dexdeps_exe) $(api_xml_description)
 
-android_cts_zip := $(HOST_OUT)/old-cts/old-android-cts.zip
+android_cts_zip := $(HOST_OUT)/cts/android-cts.zip
 cts_verifier_apk := $(call intermediates-dir-for,APPS,CtsVerifier)/package.apk
 
-$(cts-test-coverage-report): PRIVATE_TEST_CASES := $(CTS_TESTCASES_OUT)
+$(cts-test-coverage-report): PRIVATE_TEST_CASES := $(COMPATIBILITY_TESTCASES_OUT_cts)
 $(cts-test-coverage-report): PRIVATE_CTS_API_COVERAGE_EXE := $(cts_api_coverage_exe)
 $(cts-test-coverage-report): PRIVATE_DEXDEPS_EXE := $(dexdeps_exe)
 $(cts-test-coverage-report): PRIVATE_API_XML_DESC := $(api_xml_description)
@@ -55,7 +55,7 @@
 	$(call generate-coverage-report,"CTS Verifier API Coverage Report",\
 			$(PRIVATE_TEST_CASES),html)
 
-$(cts-combined-coverage-report): PRIVATE_TEST_CASES := $(foreach c, $(cts_verifier_apk) $(CTS_TESTCASES_OUT), $(c))
+$(cts-combined-coverage-report): PRIVATE_TEST_CASES := $(foreach c, $(cts_verifier_apk) $(COMPATIBILITY_TESTCASES_OUT_cts), $(c))
 $(cts-combined-coverage-report): PRIVATE_CTS_API_COVERAGE_EXE := $(cts_api_coverage_exe)
 $(cts-combined-coverage-report): PRIVATE_DEXDEPS_EXE := $(dexdeps_exe)
 $(cts-combined-coverage-report): PRIVATE_API_XML_DESC := $(api_xml_description)
@@ -63,7 +63,7 @@
 	$(call generate-coverage-report,"CTS Combined API Coverage Report",\
 			$(PRIVATE_TEST_CASES),html)
 
-$(cts-combined-xml-coverage-report): PRIVATE_TEST_CASES := $(foreach c, $(cts_verifier_apk) $(CTS_TESTCASES_OUT), $(c))
+$(cts-combined-xml-coverage-report): PRIVATE_TEST_CASES := $(foreach c, $(cts_verifier_apk) $(COMPATIBILITY_TESTCASES_OUT_cts), $(c))
 $(cts-combined-xml-coverage-report): PRIVATE_CTS_API_COVERAGE_EXE := $(cts_api_coverage_exe)
 $(cts-combined-xml-coverage-report): PRIVATE_DEXDEPS_EXE := $(dexdeps_exe)
 $(cts-combined-xml-coverage-report): PRIVATE_API_XML_DESC := $(api_xml_description)
diff --git a/apps/CameraITS/pymodules/its/image.py b/apps/CameraITS/pymodules/its/image.py
index f1539ba..4b06d96 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 b2a49aa..9766ab9 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 df904aa..4f6aed8 100644
--- a/apps/CameraITS/tests/dng_noise_model/dng_noise_model.py
+++ b/apps/CameraITS/tests/dng_noise_model/dng_noise_model.py
@@ -85,9 +85,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)
@@ -137,13 +134,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 e5fbba5..ed1426b 100644
--- a/apps/CameraITS/tests/scene0/test_metadata.py
+++ b/apps/CameraITS/tests/scene0/test_metadata.py
@@ -97,12 +97,9 @@
         print "Assert field of view: %.1f degrees" % fov
         assert 30 <= fov <= 130
 
-        if its.caps.lens_approx_calibrated(props):
-            diopter_hyperfocal = props["android.lens.info.hyperfocalDistance"]
-            if diopter_hyperfocal != 0.0:
-                hyperfocal = 1.0 / diopter_hyperfocal
-                print "Assert hyperfocal distance: %.2f m" % hyperfocal
-                assert 0.02 <= hyperfocal
+        hyperfocal = 1.0 / props["android.lens.info.hyperfocalDistance"]
+        print "Assert hyperfocal distance: %.2f m" % hyperfocal
+        assert 0.02 <= hyperfocal
 
 
 def getval(expr, default=None):
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 8ff7f73..9d212cc 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
@@ -85,7 +87,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
@@ -154,8 +156,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
@@ -180,14 +182,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/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/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 475a95b..ac07b60 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -18,9 +18,9 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
       package="com.android.cts.verifier"
       android:versionCode="5"
-      android:versionName="7.0_r0">
+      android:versionName="7.1_r1">
 
-    <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="24"/>
+    <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="25"/>
 
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
     <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"/>
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 49d249d..0c77914 100755
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -2215,8 +2215,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
@@ -2227,13 +2226,24 @@
         Device should have a sim card to perform this test.
         Please press the Set restriction button to set the user restriction.
         Then press Go to open the Cellular network page in Settings.
-        Confirm that:\n
-        \n
+        Confirm that:\n\n
         - Data roaming is disabled.\n
-        - Enabling data roaming is not possible and triggers a support message.\n
-        \n
+        - Enabling data roaming is not possible and triggers a support message.\n\n
         Use the Back button to return to this page.
     </string>
+    <string name="device_owner_disallow_factory_reset">Disallow factory reset</string>
+    <string name="device_owner_disallow_factory_reset_info">
+        Please press the Set button to set the user restriction.\n
+        1. Go to the factory reset settings. It is often located in \"Backup &amp; reset\" settings.\n
+        Confirm that:\n
+           - Factory data reset is disabled.\n
+           - Pressing factory data reset is not possible and triggers a support message.\n\n
+        2. Go to OEM unlocking settings, if this device has this Settings option. It is often located under \"Developer options\".\n
+        Confirm that:\n
+           - Oem Unlocking is disabled.\n
+           - Enabling Oem unlocking is not possible and triggers a support message.\n\n
+        Return back to this page.
+    </string>
     <string name="device_owner_user_restriction_set">Set restriction</string>
     <string name="device_owner_settings_go">Go</string>
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java
index 724f03d..024854c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java
@@ -68,6 +68,7 @@
     private static final String DISALLOW_USB_FILE_TRANSFER_ID = "DISALLOW_USB_FILE_TRANSFER";
     private static final String SET_USER_ICON_TEST_ID = "SET_USER_ICON";
     private static final String DISALLOW_DATA_ROAMING_ID = "DISALLOW_DATA_ROAMING";
+    private static final String DISALLOW_FACTORY_RESET_ID = "DISALLOW_FACTORY_RESET";
     private static final String POLICY_TRANSPARENCY_TEST_ID = "POLICY_TRANSPARENCY";
     private static final String REMOVE_DEVICE_OWNER_TEST_ID = "REMOVE_DEVICE_OWNER";
 
@@ -201,6 +202,16 @@
                                     new Intent(Settings.ACTION_DATA_ROAMING_SETTINGS))}));
         }
 
+        // DISALLOW_FACTORY_RESET
+        adapter.add(createInteractiveTestItem(this, DISALLOW_FACTORY_RESET_ID,
+                R.string.device_owner_disallow_factory_reset,
+                R.string.device_owner_disallow_factory_reset_info,
+                new ButtonInfo[] {
+                        new ButtonInfo(
+                                R.string.device_owner_user_restriction_set,
+                                createSetUserRestrictionIntent(
+                                        UserManager.DISALLOW_FACTORY_RESET))}));
+
         // DISALLOW_CONFIG_BLUETOOTH
         if (packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
             adapter.add(createInteractiveTestItem(this, DISALLOW_CONFIG_BT_ID,
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/util/CollectorUtil.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/util/CollectorUtil.java
index a664515..1321f22 100644
--- 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
@@ -177,4 +177,4 @@
         newJsonBuilder.append("}");
         return newJsonBuilder.toString();
     }
-}
\ No newline at end of file
+}
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
index d5b71fa..5e06d26 100644
--- 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
@@ -47,5 +47,4 @@
         String reformattedJson = CollectorUtil.reformatJsonString(UNFORMATTED_JSON);
         assertEquals(reformattedJson, REFORMATTED_JSON);
     }
-
-}
\ No newline at end of file
+}
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v1-only-with-rsa-1024-cert-not-der.apk b/hostsidetests/appsecurity/res/pkgsigverify/v1-only-with-rsa-1024-cert-not-der.apk
new file mode 100644
index 0000000..28c8e0c
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v1-only-with-rsa-1024-cert-not-der.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v1-only-with-rsa-1024.apk b/hostsidetests/appsecurity/res/pkgsigverify/v1-only-with-rsa-1024.apk
new file mode 100644
index 0000000..c1dff8a
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v1-only-with-rsa-1024.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v2-only-with-rsa-pkcs1-sha256-1024-cert-not-der.apk b/hostsidetests/appsecurity/res/pkgsigverify/v2-only-with-rsa-pkcs1-sha256-1024-cert-not-der.apk
new file mode 100644
index 0000000..ada1b00
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v2-only-with-rsa-pkcs1-sha256-1024-cert-not-der.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java
index 6a47676..c0c7dc0 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java
@@ -355,6 +355,70 @@
         assertInstallSucceeds("v1-only-with-rsa-pkcs1-sha1-2048.apk");
     }
 
+    public void testV1SchemeSignatureCertNotReencoded() throws Exception {
+        // Regression test for b/30148997 and b/18228011. When PackageManager does not preserve the
+        // original encoded form of signing certificates, bad things happen, such as rejection of
+        // completely valid updates to apps. The issue in b/30148997 and b/18228011 was that
+        // PackageManager started re-encoding signing certs into DER. This normally produces exactly
+        // the original form because X.509 certificates are supposed to be DER-encoded. However, a
+        // small fraction of Android apps uses X.509 certificates which are not DER-encoded. For
+        // such apps, re-encoding into DER changes the serialized form of the certificate, creating
+        // a mismatch with the serialized form stored in the PackageManager database, leading to the
+        // rejection of updates for the app.
+        //
+        // The signing certs of the two APKs differ only in how the cert's signature is encoded.
+        // From Android's perspective, these two APKs are signed by different entities and thus
+        // cannot be used to update one another. If signature verification code re-encodes certs
+        // into DER, both certs will be exactly the same and Android will accept these APKs as
+        // updates of each other. This test is thus asserting that the two APKs are not accepted as
+        // updates of each other.
+        //
+        // * v1-only-with-rsa-1024.apk cert's signature is DER-encoded
+        // * v1-only-with-rsa-1024-cert-not-der.apk cert's signature is not DER-encoded. It is
+        //   BER-encoded, with length encoded as two bytes instead of just one.
+        //   v1-only-with-rsa-1024-cert-not-der.apk META-INF/CERT.RSA was obtained from
+        //   v1-only-with-rsa-1024.apk META-INF/CERT.RSA by manually modifying the ASN.1 structure.
+        assertInstallSucceeds("v1-only-with-rsa-1024.apk");
+        assertInstallFailsWithError(
+                "v1-only-with-rsa-1024-cert-not-der.apk", "signatures do not match");
+
+        uninstallPackage();
+        assertInstallSucceeds("v1-only-with-rsa-1024-cert-not-der.apk");
+        assertInstallFailsWithError("v1-only-with-rsa-1024.apk", "signatures do not match");
+    }
+
+    public void testV2SchemeSignatureCertNotReencoded() throws Exception {
+        // This test is here to catch something like b/30148997 and b/18228011 happening to the
+        // handling of APK Signature Scheme v2 signatures by PackageManager. When PackageManager
+        // does not preserve the original encoded form of signing certificates, bad things happen,
+        // such as rejection of completely valid updates to apps. The issue in b/30148997 and
+        // b/18228011 was that PackageManager started re-encoding signing certs into DER. This
+        // normally produces exactly the original form because X.509 certificates are supposed to be
+        // DER-encoded. However, a small fraction of Android apps uses X.509 certificates which are
+        // not DER-encoded. For such apps, re-encoding into DER changes the serialized form of the
+        // certificate, creating a mismatch with the serialized form stored in the PackageManager
+        // database, leading to the rejection of updates for the app.
+        //
+        // The signing certs of the two APKs differ only in how the cert's signature is encoded.
+        // From Android's perspective, these two APKs are signed by different entities and thus
+        // cannot be used to update one another. If signature verification code re-encodes certs
+        // into DER, both certs will be exactly the same and Android will accept these APKs as
+        // updates of each other. This test is thus asserting that the two APKs are not accepted as
+        // updates of each other.
+        //
+        // * v2-only-with-rsa-pkcs1-sha256-1024.apk cert's signature is DER-encoded
+        // * v2-only-with-rsa-pkcs1-sha256-1024-cert-not-der.apk cert's signature is not DER-encoded
+        //   It is BER-encoded, with length encoded as two bytes instead of just one.
+        assertInstallSucceeds("v2-only-with-rsa-pkcs1-sha256-1024.apk");
+        assertInstallFailsWithError(
+                "v2-only-with-rsa-pkcs1-sha256-1024-cert-not-der.apk", "signatures do not match");
+
+        uninstallPackage();
+        assertInstallSucceeds("v2-only-with-rsa-pkcs1-sha256-1024-cert-not-der.apk");
+        assertInstallFailsWithError(
+                "v2-only-with-rsa-pkcs1-sha256-1024.apk", "signatures do not match");
+    }
+
     private void assertInstallSucceeds(String apkFilenameInResources) throws Exception {
         String installResult = installPackageFromResource(apkFilenameInResources);
         if (installResult != null) {
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AlwaysOnVpnMultiStageTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AlwaysOnVpnMultiStageTest.java
new file mode 100644
index 0000000..f6604ee
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AlwaysOnVpnMultiStageTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.deviceandprofileowner;
+
+import android.content.pm.PackageManager;
+import android.system.ErrnoException;
+import android.system.OsConstants;
+
+import com.android.cts.deviceandprofileowner.vpn.VpnTestHelper;
+
+import static com.android.cts.deviceandprofileowner.vpn.VpnTestHelper.VPN_PACKAGE;
+import static com.android.cts.deviceandprofileowner.vpn.VpnTestHelper.TEST_ADDRESS;
+
+/**
+ * Contains methods to test always-on VPN invoked by DeviceAndProfileOwnerTest
+ */
+public class AlwaysOnVpnMultiStageTest extends BaseDeviceAdminTest {
+
+    public void testAlwaysOnSet() throws Exception {
+        // Setup always-on vpn
+        VpnTestHelper.setAndWaitForVpn(mContext, VPN_PACKAGE, /* usable */ true);
+        assertTrue(VpnTestHelper.isNetworkVpn(mContext));
+        VpnTestHelper.checkPing(TEST_ADDRESS);
+    }
+
+    public void testNetworkBlocked() throws Exception {
+        // After the vpn app being force-stop, expect that always-on package stays the same
+        assertEquals(VPN_PACKAGE, mDevicePolicyManager.getAlwaysOnVpnPackage(
+                ADMIN_RECEIVER_COMPONENT));
+        assertFalse(VpnTestHelper.isNetworkVpn(mContext));
+        // Expect the network is still locked down after the vpn app process is killed
+        try {
+            VpnTestHelper.tryPosixConnect(TEST_ADDRESS);
+            fail("sendIcmpMessage doesn't throw Exception during network lockdown");
+        } catch (ErrnoException e) {
+            // Os.connect returns ENETUNREACH errno after the vpn app process is killed
+            assertEquals(OsConstants.ENETUNREACH, e.errno);
+        }
+    }
+
+    public void testAlwaysOnVpnDisabled() throws Exception {
+        // After the vpn app being uninstalled, check that always-on vpn is null
+        assertNull(mDevicePolicyManager.getAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT));
+        assertFalse(VpnTestHelper.isNetworkVpn(mContext));
+    }
+
+    public void testSetNonExistingPackage() throws Exception {
+        assertNull(mDevicePolicyManager.getAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT));
+
+        // Verify it throws NameNotFoundException for non-existing package after uninstallation
+        try {
+            mDevicePolicyManager.setAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT, VPN_PACKAGE,
+                    true);
+            fail("setAlwaysOnVpnPackage should not accept non-vpn package");
+        } catch (PackageManager.NameNotFoundException e) {
+            // success
+        }
+
+        assertNull(mDevicePolicyManager.getAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT));
+    }
+
+    public void testCleanup() throws Exception {
+        mDevicePolicyManager.setAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT, null, false);
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AlwaysOnVpnTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AlwaysOnVpnTest.java
index 67dd941..34566a1 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AlwaysOnVpnTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AlwaysOnVpnTest.java
@@ -16,33 +16,12 @@
 
 package com.android.cts.deviceandprofileowner;
 
-import android.content.pm.PackageManager;
-import android.net.ConnectivityManager.NetworkCallback;
-import android.net.ConnectivityManager;
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.NetworkInfo;
-import android.net.NetworkRequest;
 import android.os.Bundle;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.StructPollfd;
 
-import java.io.ByteArrayOutputStream;
-import java.io.DataOutputStream;
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.Socket;
-import java.util.Arrays;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
+import com.android.cts.deviceandprofileowner.vpn.VpnTestHelper;
 
-import static android.system.OsConstants.AF_INET;
-import static android.system.OsConstants.IPPROTO_ICMP;
-import static android.system.OsConstants.POLLIN;
-import static android.system.OsConstants.SOCK_DGRAM;
+import static com.android.cts.deviceandprofileowner.vpn.VpnTestHelper.VPN_PACKAGE;
+import static com.android.cts.deviceandprofileowner.vpn.VpnTestHelper.TEST_ADDRESS;
 
 /**
  * Validates that a device owner or profile owner can set an always-on VPN without user action.
@@ -55,33 +34,18 @@
  * result of a misconfigured network.
  */
 public class AlwaysOnVpnTest extends BaseDeviceAdminTest {
-
-    private static final String VPN_PACKAGE = "com.android.cts.vpnfirewall";
-    private static final int NETWORK_TIMEOUT_MS = 5000;
-    private static final int NETWORK_SETTLE_GRACE_MS = 100;
-    private static final int SOCKET_TIMEOUT_MS = 5000;
-
     /** @see com.android.cts.vpnfirewall.ReflectorVpnService */
     public static final String RESTRICTION_ADDRESSES = "vpn.addresses";
     public static final String RESTRICTION_ROUTES = "vpn.routes";
     public static final String RESTRICTION_ALLOWED = "vpn.allowed";
     public static final String RESTRICTION_DISALLOWED = "vpn.disallowed";
 
-    private static final int ICMP_ECHO_REQUEST = 0x08;
-    private static final int ICMP_ECHO_REPLY = 0x00;
-
-    // IP address reserved for documentation by rfc5737
-    private static final String TEST_ADDRESS = "192.0.2.4";
-
-    private ConnectivityManager mConnectivityManager;
     private String mPackageName;
 
     @Override
     public void setUp() throws Exception {
         super.setUp();
         mPackageName = mContext.getPackageName();
-        mConnectivityManager =
-                (ConnectivityManager) mContext.getSystemService(mContext.CONNECTIVITY_SERVICE);
     }
 
     @Override
@@ -91,14 +55,12 @@
                 /* restrictions */ null);
         super.tearDown();
     }
-
     public void testAlwaysOnVpn() throws Exception {
         // test always-on is null by default
         assertNull(mDevicePolicyManager.getAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT));
 
-        final CountDownLatch vpnLatch = new CountDownLatch(1);
-        setAndWaitForVpn(VPN_PACKAGE, /* usable */ true);
-        checkPing(TEST_ADDRESS);
+        VpnTestHelper.setAndWaitForVpn(mContext, VPN_PACKAGE, /* usable */ true);
+        VpnTestHelper.checkPing(TEST_ADDRESS);
     }
 
     public void testAllowedApps() throws Exception {
@@ -106,8 +68,8 @@
         restrictions.putStringArray(RESTRICTION_ALLOWED, new String[] {mPackageName});
         mDevicePolicyManager.setApplicationRestrictions(ADMIN_RECEIVER_COMPONENT, VPN_PACKAGE,
                 restrictions);
-        setAndWaitForVpn(VPN_PACKAGE, /* usable */ true);
-        assertTrue(isNetworkVpn());
+        VpnTestHelper.setAndWaitForVpn(mContext, VPN_PACKAGE, /* usable */ true);
+        assertTrue(VpnTestHelper.isNetworkVpn(mContext));
     }
 
     public void testDisallowedApps() throws Exception {
@@ -115,110 +77,24 @@
         restrictions.putStringArray(RESTRICTION_DISALLOWED, new String[] {mPackageName});
         mDevicePolicyManager.setApplicationRestrictions(ADMIN_RECEIVER_COMPONENT, VPN_PACKAGE,
                 restrictions);
-        setAndWaitForVpn(VPN_PACKAGE, /* usable */ false);
-        assertFalse(isNetworkVpn());
+        VpnTestHelper.setAndWaitForVpn(mContext, VPN_PACKAGE, /* usable */ false);
+        assertFalse(VpnTestHelper.isNetworkVpn(mContext));
     }
 
-    private void setAndWaitForVpn(String packageName, boolean usable) {
-        final CountDownLatch vpnLatch = new CountDownLatch(1);
-        final NetworkRequest request = new NetworkRequest.Builder()
-                .addTransportType(NetworkCapabilities.TRANSPORT_VPN)
-                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
-                .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                .build();
-        final ConnectivityManager.NetworkCallback callback
-                = new ConnectivityManager.NetworkCallback() {
-            @Override
-            public void onAvailable(Network net) {
-                vpnLatch.countDown();
-            }
-        };
-        mConnectivityManager.registerNetworkCallback(request, callback);
+    public void testSetNonVpnAlwaysOn() throws Exception {
+        // test always-on is null by default
+        assertNull(mDevicePolicyManager.getAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT));
+
+        // Treat this CTS DPC as an non-vpn app, since it doesn't register
+        // android.net.VpnService intent filter in AndroidManifest.xml.
         try {
-            mDevicePolicyManager.setAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT, VPN_PACKAGE, true);
-            assertEquals(VPN_PACKAGE, mDevicePolicyManager.getAlwaysOnVpnPackage(
-                    ADMIN_RECEIVER_COMPONENT));
-            if (!vpnLatch.await(NETWORK_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
-                fail("Took too long waiting to establish a VPN-backed connection");
-            }
-            // Give the VPN a moment to start transmitting data.
-            Thread.sleep(NETWORK_SETTLE_GRACE_MS);
-        } catch (InterruptedException | PackageManager.NameNotFoundException e) {
-            fail("Failed to send ping: " + e);
-        } finally {
-            mConnectivityManager.unregisterNetworkCallback(callback);
+            mDevicePolicyManager.setAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT, mPackageName,
+                    true);
+            fail("setAlwaysOnVpnPackage should not accept non-vpn package");
+        } catch (UnsupportedOperationException e) {
+            // success
         }
-
-        // Do we have a network?
-        NetworkInfo vpnInfo = mConnectivityManager.getNetworkInfo(ConnectivityManager.TYPE_VPN);
-        assertTrue(vpnInfo != null);
-
-        // Is it usable?
-        assertEquals(usable, vpnInfo.isConnected());
-    }
-
-    private boolean isNetworkVpn() {
-        Network network = mConnectivityManager.getActiveNetwork();
-        NetworkCapabilities capabilities = mConnectivityManager.getNetworkCapabilities(network);
-        return capabilities != null && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN);
-    }
-
-    private static void checkPing(String host) throws ErrnoException, IOException {
-        FileDescriptor socket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
-
-        // Create an ICMP message
-        final int identifier = 0x7E57;
-        final String message = "test packet";
-        byte[] echo = createIcmpMessage(ICMP_ECHO_REQUEST, 0x00, identifier, 0, message.getBytes());
-
-        // Send the echo packet.
-        int port = new InetSocketAddress(0).getPort();
-        Os.connect(socket, InetAddress.getByName(host), port);
-        Os.write(socket, echo, 0, echo.length);
-
-        // Expect a reply.
-        StructPollfd pollfd = new StructPollfd();
-        pollfd.events = (short) POLLIN;
-        pollfd.fd = socket;
-        int ret = Os.poll(new StructPollfd[] { pollfd }, SOCKET_TIMEOUT_MS);
-        assertEquals("Expected reply after sending ping", 1, ret);
-
-        byte[] reply = new byte[echo.length];
-        int read = Os.read(socket, reply, 0, echo.length);
-        assertEquals(echo.length, read);
-
-        // Ignore control type differences since echo=8, reply=0.
-        assertEquals(echo[0], ICMP_ECHO_REQUEST);
-        assertEquals(reply[0], ICMP_ECHO_REPLY);
-        echo[0] = 0;
-        reply[0] = 0;
-
-        // Fix ICMP ID which kernel will have changed on the way out.
-        InetSocketAddress local = (InetSocketAddress) Os.getsockname(socket);
-        port = local.getPort();
-        echo[4] = (byte) ((port >> 8) & 0xFF);
-        echo[5] = (byte) (port & 0xFF);
-
-        // Ignore checksum differences since the types are not supposed to match.
-        echo[2] = echo[3] = 0;
-        reply[2] = reply[3] = 0;
-
-        assertTrue("Packet contents do not match."
-                + "\nEcho packet:  " + Arrays.toString(echo)
-                + "\nReply packet: " + Arrays.toString(reply), Arrays.equals(echo, reply));
-    }
-
-    private static byte[] createIcmpMessage(int type, int code, int extra1, int extra2,
-            byte[] data) throws IOException {
-        ByteArrayOutputStream output = new ByteArrayOutputStream();
-        DataOutputStream stream = new DataOutputStream(output);
-        stream.writeByte(type);
-        stream.writeByte(code);
-        stream.writeShort(/* checksum */ 0);
-        stream.writeShort((short) extra1);
-        stream.writeShort((short) extra2);
-        stream.write(data, 0, data.length);
-        return output.toByteArray();
+        assertNull(mDevicePolicyManager.getAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT));
     }
 }
 
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/BaseUserRestrictionsTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/BaseUserRestrictionsTest.java
index a7d8110..8b62604 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/BaseUserRestrictionsTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/BaseUserRestrictionsTest.java
@@ -85,7 +85,8 @@
 
     public static final String[] HIDDEN_AND_PROHIBITED = new String[] {
             "no_record_audio",
-            "no_wallpaper"
+            "no_wallpaper",
+            "no_oem_unlock"
     };
 
     protected void assertLayeredRestriction(String restriction, boolean expected) {
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/vpn/VpnTestHelper.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/vpn/VpnTestHelper.java
new file mode 100644
index 0000000..3072251
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/vpn/VpnTestHelper.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.deviceandprofileowner.vpn;
+
+import android.annotation.TargetApi;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.net.NetworkRequest;
+import android.os.Build.VERSION_CODES;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructPollfd;
+
+import com.android.cts.deviceandprofileowner.BaseDeviceAdminTest;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.IPPROTO_ICMP;
+import static android.system.OsConstants.POLLIN;
+import static android.system.OsConstants.SOCK_DGRAM;
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
+/**
+ * Helper class to test vpn status
+ */
+@TargetApi(VERSION_CODES.N)
+public class VpnTestHelper {
+    public static final String VPN_PACKAGE = "com.android.cts.vpnfirewall";
+
+    // IP address reserved for documentation by rfc5737
+    public static final String TEST_ADDRESS = "192.0.2.4";
+
+    private static final int SOCKET_TIMEOUT_MS = 5000;
+    private static final int ICMP_ECHO_REQUEST = 0x08;
+    private static final int ICMP_ECHO_REPLY = 0x00;
+    private static final int NETWORK_TIMEOUT_MS = 5000;
+    private static final int NETWORK_SETTLE_GRACE_MS = 100;
+    private static final ComponentName ADMIN_RECEIVER_COMPONENT =
+            BaseDeviceAdminTest.ADMIN_RECEIVER_COMPONENT;
+
+    public static void setAndWaitForVpn(Context context, String packageName, boolean usable) {
+        ConnectivityManager connectivityManager =
+                context.getSystemService(ConnectivityManager.class);
+        DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
+        final CountDownLatch vpnLatch = new CountDownLatch(1);
+        final NetworkRequest request = new NetworkRequest.Builder()
+                .addTransportType(NetworkCapabilities.TRANSPORT_VPN)
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                .build();
+        final ConnectivityManager.NetworkCallback callback
+                = new ConnectivityManager.NetworkCallback() {
+            @Override
+            public void onAvailable(Network net) {
+                vpnLatch.countDown();
+            }
+        };
+        connectivityManager.registerNetworkCallback(request, callback);
+        try {
+            dpm.setAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT, packageName, true);
+            assertEquals(packageName, dpm.getAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT));
+            if (!vpnLatch.await(NETWORK_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                fail("Took too long waiting to establish a VPN-backed connection");
+            }
+            // Give the VPN a moment to start transmitting data.
+            Thread.sleep(NETWORK_SETTLE_GRACE_MS);
+        } catch (InterruptedException | PackageManager.NameNotFoundException e) {
+            fail("Failed to send ping: " + e);
+        } finally {
+            connectivityManager.unregisterNetworkCallback(callback);
+        }
+
+        // Do we have a network?
+        NetworkInfo vpnInfo = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_VPN);
+        assertTrue(vpnInfo != null);
+
+        // Is it usable?
+        assertEquals(usable, vpnInfo.isConnected());
+    }
+
+
+    public static boolean isNetworkVpn(Context context) {
+        ConnectivityManager connectivityManager =
+                (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+        Network network = connectivityManager.getActiveNetwork();
+        NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network);
+        return capabilities != null && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN);
+    }
+
+    public static void checkPing(String host) throws ErrnoException, IOException {
+        FileDescriptor socket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
+
+        // Create an ICMP message
+        final int identifier = 0x7E57;
+        final String message = "test packet";
+        byte[] echo = createIcmpMessage(ICMP_ECHO_REQUEST, 0x00, identifier, 0, message.getBytes());
+
+        // Send the echo packet.
+        int port = new InetSocketAddress(0).getPort();
+        Os.connect(socket, InetAddress.getByName(host), port);
+        Os.write(socket, echo, 0, echo.length);
+
+        // Expect a reply.
+        StructPollfd pollfd = new StructPollfd();
+        pollfd.events = (short) POLLIN;
+        pollfd.fd = socket;
+        int ret = Os.poll(new StructPollfd[] { pollfd }, SOCKET_TIMEOUT_MS);
+        assertEquals("Expected reply after sending ping", 1, ret);
+
+        byte[] reply = new byte[echo.length];
+        int read = Os.read(socket, reply, 0, echo.length);
+        assertEquals(echo.length, read);
+
+        // Ignore control type differences since echo=8, reply=0.
+        assertEquals(echo[0], ICMP_ECHO_REQUEST);
+        assertEquals(reply[0], ICMP_ECHO_REPLY);
+        echo[0] = 0;
+        reply[0] = 0;
+
+        // Fix ICMP ID which kernel will have changed on the way out.
+        InetSocketAddress local = (InetSocketAddress) Os.getsockname(socket);
+        port = local.getPort();
+        echo[4] = (byte) ((port >> 8) & 0xFF);
+        echo[5] = (byte) (port & 0xFF);
+
+        // Ignore checksum differences since the types are not supposed to match.
+        echo[2] = echo[3] = 0;
+        reply[2] = reply[3] = 0;
+
+        assertTrue("Packet contents do not match."
+                + "\nEcho packet:  " + Arrays.toString(echo)
+                + "\nReply packet: " + Arrays.toString(reply), Arrays.equals(echo, reply));
+
+        // Close socket if the test pass. Otherwise, any error will kill the process.
+        Os.close(socket);
+    }
+
+    public static void tryPosixConnect(String host) throws ErrnoException, IOException {
+        FileDescriptor socket = null;
+        try {
+            socket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
+            int port = new InetSocketAddress(0).getPort();
+            Os.connect(socket, InetAddress.getByName(host), port);
+        } finally {
+            if (socket != null) {
+                Os.close(socket);
+            }
+        }
+    }
+
+    private static byte[] createIcmpMessage(int type, int code, int extra1, int extra2,
+            byte[] data) throws IOException {
+        ByteArrayOutputStream output = new ByteArrayOutputStream();
+        DataOutputStream stream = new DataOutputStream(output);
+        stream.writeByte(type);
+        stream.writeByte(code);
+        stream.writeShort(/* checksum */ 0);
+        stream.writeShort((short) extra1);
+        stream.writeShort((short) extra2);
+        stream.write(data, 0, data.length);
+        return output.toByteArray();
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
index d366f49..70206ee 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 9cf6fc8..d36e473 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
@@ -106,6 +106,9 @@
             getDevice().uninstallPackage(INTENT_RECEIVER_PKG);
             getDevice().uninstallPackage(INTENT_SENDER_PKG);
             getDevice().uninstallPackage(CUSTOMIZATION_APP_PKG);
+
+            // Press the HOME key to close any alart dialog that may be shown.
+            getDevice().executeShellCommand("input keyevent 3");
         }
         super.tearDown();
     }
@@ -175,6 +178,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/devicepolicy/src/com/android/cts/devicepolicy/UserRestrictionsTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/UserRestrictionsTest.java
index bc61a81..789ce87 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/UserRestrictionsTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/UserRestrictionsTest.java
@@ -45,8 +45,7 @@
                         removeAdmin(DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS,
                                 mDeviceOwnerUserId));
                 assertTrue("Some user restrictions are still set",
-                        runTests("userrestrictions.CheckNoOwnerRestrictionsTest",
-                                mDeviceOwnerUserId));
+                        runTests("userrestrictions.CheckNoOwnerRestrictionsTest", mDeviceOwnerUserId));
             }
 
             // DO/PO might have set DISALLOW_REMOVE_USER, so it needs to be done after removing
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 cc05b04..fc674a7 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
@@ -131,16 +131,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 46d243e..272c8af 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;
@@ -288,60 +300,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 {
@@ -787,10 +814,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 6e9aead..ee922ff 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 534e2ea..2417da7 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));
@@ -96,7 +97,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);
@@ -119,7 +119,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);
@@ -146,7 +145,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 ae22720..19fc84a 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,24 +57,23 @@
     }
 
     public void testLaunchToSide() throws Exception {
-        launchActivityInDockStack(LAUNCH_TO_SIDE_ACTIVITY_NAME);
-        mAmWmState.computeState(mDevice, new String[] {LAUNCH_TO_SIDE_ACTIVITY_NAME});
-        launchActivityToSide(LAUNCH_TO_SIDE_ACTIVITY_NAME);
+        launchActivityInDockStack(LAUNCHING_ACTIVITY);
+        mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
+        launchActivityToSide();
         mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
-
         mAmWmState.assertContainsStack(
                 "Must contain fullscreen stack.", FULLSCREEN_WORKSPACE_STACK_ID);
         mAmWmState.assertContainsStack("Must contain docked stack.", DOCKED_STACK_ID);
     }
 
     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};
-        mAmWmState.computeState(mDevice, new String[] {LAUNCH_TO_SIDE_ACTIVITY_NAME});
+        mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
 
         // 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();
@@ -93,7 +91,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();
@@ -104,14 +102,13 @@
     }
 
     public void testLaunchToSideMultiple() throws Exception {
-        launchActivityInDockStack(LAUNCH_TO_SIDE_ACTIVITY_NAME);
-        mAmWmState.computeState(mDevice, new String[] {LAUNCH_TO_SIDE_ACTIVITY_NAME});
-
+        launchActivityInDockStack(LAUNCHING_ACTIVITY);
+        mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
         final String[] waitForActivitiesVisible =
-            new String[] {TEST_ACTIVITY_NAME, LAUNCH_TO_SIDE_ACTIVITY_NAME};
+            new String[] {TEST_ACTIVITY_NAME, LAUNCHING_ACTIVITY};
 
         // 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();
@@ -120,7 +117,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();
@@ -146,14 +143,14 @@
 
     private void launchTargetToSide(String targetActivityName,
                                     boolean taskCountMustIncrement) throws Exception {
-        launchActivityInDockStack(LAUNCH_TO_SIDE_ACTIVITY_NAME);
-        mAmWmState.computeState(mDevice, new String[] {LAUNCH_TO_SIDE_ACTIVITY_NAME});
+        launchActivityInDockStack(LAUNCHING_ACTIVITY);
+        mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
 
         final String[] waitForActivitiesVisible =
-            new String[] {targetActivityName, LAUNCH_TO_SIDE_ACTIVITY_NAME};
+            new String[] {targetActivityName, LAUNCHING_ACTIVITY};
 
         // Launch activity to side with data.
-        launchActivityToSide(LAUNCH_TO_SIDE_ACTIVITY_NAME, true, false, targetActivityName);
+        launchActivityToSide(true, false, targetActivityName);
         mAmWmState.computeState(mDevice, waitForActivitiesVisible);
         mAmWmState.assertContainsStack(
                 "Must contain fullscreen stack.", FULLSCREEN_WORKSPACE_STACK_ID);
@@ -164,7 +161,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();
@@ -182,7 +179,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();
@@ -201,13 +198,13 @@
     }
 
     public void testLaunchToSideMultipleWithFlag() throws Exception {
-        launchActivityInDockStack(LAUNCH_TO_SIDE_ACTIVITY_NAME);
-        mAmWmState.computeState(mDevice, new String[] {LAUNCH_TO_SIDE_ACTIVITY_NAME});
+        launchActivityInDockStack(LAUNCHING_ACTIVITY);
+        mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
         final String[] waitForActivitiesVisible =
-            new String[] {LAUNCH_TO_SIDE_ACTIVITY_NAME, TEST_ACTIVITY_NAME};
+            new String[] {LAUNCHING_ACTIVITY, 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();
@@ -216,7 +213,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();
@@ -230,9 +227,9 @@
     }
 
     public void testRotationWhenDocked() throws Exception {
-        launchActivityInDockStack(LAUNCH_TO_SIDE_ACTIVITY_NAME);
-        mAmWmState.computeState(mDevice, new String[] {LAUNCH_TO_SIDE_ACTIVITY_NAME});
-        launchActivityToSide(LAUNCH_TO_SIDE_ACTIVITY_NAME);
+        launchActivityInDockStack(LAUNCHING_ACTIVITY);
+        mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
+        launchActivityToSide();
         mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
         mAmWmState.assertContainsStack(
                 "Must contain fullscreen stack.", FULLSCREEN_WORKSPACE_STACK_ID);
@@ -241,7 +238,7 @@
         // Rotate device single steps (90°) 0-1-2-3.
         // Each time we compute the state we implicitly assert valid bounds.
         String[] waitForActivitiesVisible =
-            new String[] {LAUNCH_TO_SIDE_ACTIVITY_NAME, TEST_ACTIVITY_NAME};
+            new String[] {LAUNCHING_ACTIVITY, TEST_ACTIVITY_NAME};
         setDeviceRotation(0);
         mAmWmState.computeState(mDevice, waitForActivitiesVisible);
         setDeviceRotation(1);
@@ -265,9 +262,9 @@
     }
 
     public void testRotationWhenDockedWhileLocked() throws Exception {
-        launchActivityInDockStack(LAUNCH_TO_SIDE_ACTIVITY_NAME);
-        mAmWmState.computeState(mDevice, new String[] {LAUNCH_TO_SIDE_ACTIVITY_NAME});
-        launchActivityToSide(LAUNCH_TO_SIDE_ACTIVITY_NAME);
+        launchActivityInDockStack(LAUNCHING_ACTIVITY);
+        mAmWmState.computeState(mDevice, new String[] {LAUNCHING_ACTIVITY});
+        launchActivityToSide();
         mAmWmState.computeState(mDevice, new String[] {TEST_ACTIVITY_NAME});
         mAmWmState.assertSanity();
         mAmWmState.assertContainsStack(
@@ -275,7 +272,7 @@
         mAmWmState.assertContainsStack("Must contain docked stack.", DOCKED_STACK_ID);
 
         String[] waitForActivitiesVisible =
-            new String[] {LAUNCH_TO_SIDE_ACTIVITY_NAME, TEST_ACTIVITY_NAME};
+            new String[] {LAUNCHING_ACTIVITY, TEST_ACTIVITY_NAME};
         lockDevice();
         setDeviceRotation(0);
         unlockDevice();
@@ -365,29 +362,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/shortcuts/Android.mk b/hostsidetests/shortcuts/Android.mk
new file mode 100644
index 0000000..3d02f9c
--- /dev/null
+++ b/hostsidetests/shortcuts/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/hostsidetests/shortcuts/deviceside/Android.mk b/hostsidetests/shortcuts/deviceside/Android.mk
new file mode 100644
index 0000000..3d02f9c
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/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/hostsidetests/shortcuts/deviceside/backup/Android.mk b/hostsidetests/shortcuts/deviceside/backup/Android.mk
new file mode 100644
index 0000000..3d02f9c
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/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/hostsidetests/shortcuts/deviceside/backup/launcher1/Android.mk b/hostsidetests/shortcuts/deviceside/backup/launcher1/Android.mk
new file mode 100644
index 0000000..a463b59
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher1/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 := CtsShortcutBackupLauncher1
+
+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-minus-junit4 \
+    ctsdeviceutil \
+    ctstestrunner \
+    ub-uiautomator \
+    ShortcutManagerTestUtils
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher1/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/backup/launcher1/AndroidManifest.xml
new file mode 100644
index 0000000..75b3ca0
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher1/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?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.shortcut.backup.launcher1">
+
+    <application>
+        <activity android:name="MainActivity" android:exported="true" >
+            <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>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.content.pm.cts.shortcut.backup.launcher1"  />
+</manifest>
+
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher1/src/android/content/pm/cts/shortcut/backup/launcher1/MainActivity.java b/hostsidetests/shortcuts/deviceside/backup/launcher1/src/android/content/pm/cts/shortcut/backup/launcher1/MainActivity.java
new file mode 100644
index 0000000..3d8a1de
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher1/src/android/content/pm/cts/shortcut/backup/launcher1/MainActivity.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.shortcut.backup.launcher1;
+
+import android.app.Activity;
+
+public class MainActivity extends Activity {
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher1/src/android/content/pm/cts/shortcut/backup/launcher1/ShortcutManagerPostBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/launcher1/src/android/content/pm/cts/shortcut/backup/launcher1/ShortcutManagerPostBackupTest.java
new file mode 100644
index 0000000..b82bb1d
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher1/src/android/content/pm/cts/shortcut/backup/launcher1/ShortcutManagerPostBackupTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.shortcut.backup.launcher1;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+
+import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
+
+public class ShortcutManagerPostBackupTest extends ShortcutManagerDeviceTestBase {
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        setAsDefaultLauncher(MainActivity.class);
+    }
+
+    public void testWithUninstall_beforeAppRestore() {
+        assertWith(getPackageShortcuts(ShortcutManagerPreBackupTest.PUBLISHER1_PKG)).isEmpty();
+        assertWith(getPackageShortcuts(ShortcutManagerPreBackupTest.PUBLISHER2_PKG)).isEmpty();
+        assertWith(getPackageShortcuts(ShortcutManagerPreBackupTest.PUBLISHER3_PKG)).isEmpty();
+    }
+
+    public void testWithUninstall_afterAppRestore() {
+        assertWith(getPackageShortcuts(ShortcutManagerPreBackupTest.PUBLISHER1_PKG))
+                .haveIds("ms1", "ms2", "s1")
+                .areAllEnabled()
+
+                .selectByIds("s1", "ms1")
+                .areAllPinned()
+
+                .revertToOriginalList()
+                .selectByIds("ms2")
+                .areAllNotPinned();
+
+        // Note s3 and ms2 were disabled before backup, so they were not backed up.
+        assertWith(getPackageShortcuts(ShortcutManagerPreBackupTest.PUBLISHER2_PKG))
+                .haveIds("ms1", "ms2", "s1", "s2")
+                .areAllEnabled()
+
+                .selectByIds("s1", "s2")
+                .areAllPinned()
+
+                .revertToOriginalList()
+                .selectByIds("ms1")
+                .areAllNotPinned();
+
+        assertWith(getPackageShortcuts(ShortcutManagerPreBackupTest.PUBLISHER3_PKG))
+                .haveIds("ms1", "ms2")
+                .areAllEnabled()
+                .areAllNotPinned(); // P3 doesn't get backed up, so no longer pinned.
+    }
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher1/src/android/content/pm/cts/shortcut/backup/launcher1/ShortcutManagerPreBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/launcher1/src/android/content/pm/cts/shortcut/backup/launcher1/ShortcutManagerPreBackupTest.java
new file mode 100644
index 0000000..56639ce
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher1/src/android/content/pm/cts/shortcut/backup/launcher1/ShortcutManagerPreBackupTest.java
@@ -0,0 +1,45 @@
+/*
+ * 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.shortcut.backup.launcher1;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+
+import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+@SmallTest
+public class ShortcutManagerPreBackupTest extends ShortcutManagerDeviceTestBase {
+    static final String PUBLISHER1_PKG =
+            "android.content.pm.cts.shortcut.backup.publisher1";
+    static final String PUBLISHER2_PKG =
+            "android.content.pm.cts.shortcut.backup.publisher2";
+    static final String PUBLISHER3_PKG =
+            "android.content.pm.cts.shortcut.backup.publisher3";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        setAsDefaultLauncher(MainActivity.class);
+    }
+
+    public void testPreBackup() {
+        getLauncherApps().pinShortcuts(PUBLISHER1_PKG, list("s1", "ms1"), getUserHandle());
+        getLauncherApps().pinShortcuts(PUBLISHER2_PKG, list("s1", "s2", "ms2"), getUserHandle());
+        getLauncherApps().pinShortcuts(PUBLISHER3_PKG, list("s1", "s2", "ms1", "ms2"),
+                getUserHandle());
+    }
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher2/Android.mk b/hostsidetests/shortcuts/deviceside/backup/launcher2/Android.mk
new file mode 100644
index 0000000..2f37101
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher2/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 := CtsShortcutBackupLauncher2
+
+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-minus-junit4 \
+    ctsdeviceutil \
+    ctstestrunner \
+    ub-uiautomator \
+    ShortcutManagerTestUtils
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher2/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/backup/launcher2/AndroidManifest.xml
new file mode 100644
index 0000000..71ffc61
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher2/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?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.shortcut.backup.launcher2">
+
+    <application>
+        <activity android:name="MainActivity" android:exported="true" >
+            <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>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.content.pm.cts.shortcut.backup.launcher2"  />
+</manifest>
+
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher2/src/android/content/pm/cts/shortcut/backup/launcher2/MainActivity.java b/hostsidetests/shortcuts/deviceside/backup/launcher2/src/android/content/pm/cts/shortcut/backup/launcher2/MainActivity.java
new file mode 100644
index 0000000..f418ea1
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher2/src/android/content/pm/cts/shortcut/backup/launcher2/MainActivity.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.shortcut.backup.launcher2;
+
+import android.app.Activity;
+
+public class MainActivity extends Activity {
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher2/src/android/content/pm/cts/shortcut/backup/launcher2/ShortcutManagerPostBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/launcher2/src/android/content/pm/cts/shortcut/backup/launcher2/ShortcutManagerPostBackupTest.java
new file mode 100644
index 0000000..75c79c5
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher2/src/android/content/pm/cts/shortcut/backup/launcher2/ShortcutManagerPostBackupTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.shortcut.backup.launcher2;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+
+import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
+
+public class ShortcutManagerPostBackupTest extends ShortcutManagerDeviceTestBase {
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        setAsDefaultLauncher(MainActivity.class);
+    }
+
+    public void testWithUninstall_afterAppRestore() {
+        assertWith(getPackageShortcuts(ShortcutManagerPreBackupTest.PUBLISHER1_PKG))
+                .haveIds("ms1", "ms2", "s3")
+                .areAllEnabled()
+
+                .selectByIds("s3", "ms2")
+                .areAllPinned()
+
+                .revertToOriginalList()
+                .selectByIds("ms1")
+                .areAllNotPinned();
+
+        // Note s3 and ms2 were disabled before backup, so they were not backed up.
+        assertWith(getPackageShortcuts(ShortcutManagerPreBackupTest.PUBLISHER2_PKG))
+                .haveIds("ms1", "ms2", "s2")
+                .areAllEnabled()
+
+                .selectByIds("s2", "ms1")
+                .areAllPinned()
+
+                .revertToOriginalList()
+                .selectByIds("ms2")
+                .areAllNotPinned();
+
+        assertWith(getPackageShortcuts(ShortcutManagerPreBackupTest.PUBLISHER3_PKG))
+                .haveIds("ms1", "ms2")
+                .areAllEnabled();
+    }
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher2/src/android/content/pm/cts/shortcut/backup/launcher2/ShortcutManagerPreBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/launcher2/src/android/content/pm/cts/shortcut/backup/launcher2/ShortcutManagerPreBackupTest.java
new file mode 100644
index 0000000..b0e0e2c
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher2/src/android/content/pm/cts/shortcut/backup/launcher2/ShortcutManagerPreBackupTest.java
@@ -0,0 +1,45 @@
+/*
+ * 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.shortcut.backup.launcher2;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+
+import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+@SmallTest
+public class ShortcutManagerPreBackupTest extends ShortcutManagerDeviceTestBase {
+    static final String PUBLISHER1_PKG =
+            "android.content.pm.cts.shortcut.backup.publisher1";
+    static final String PUBLISHER2_PKG =
+            "android.content.pm.cts.shortcut.backup.publisher2";
+    static final String PUBLISHER3_PKG =
+            "android.content.pm.cts.shortcut.backup.publisher3";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        setAsDefaultLauncher(MainActivity.class);
+    }
+
+    public void testPreBackup() {
+        getLauncherApps().pinShortcuts(PUBLISHER1_PKG, list("s3", "ms2"), getUserHandle());
+        getLauncherApps().pinShortcuts(PUBLISHER2_PKG, list("s2", "s3", "ms1"), getUserHandle());
+        getLauncherApps().pinShortcuts(PUBLISHER3_PKG, list("s2", "s3", "ms1", "ms2"),
+                getUserHandle());
+    }
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher3/Android.mk b/hostsidetests/shortcuts/deviceside/backup/launcher3/Android.mk
new file mode 100644
index 0000000..3bbd906
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher3/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 := CtsShortcutBackupLauncher3
+
+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-minus-junit4 \
+    ctsdeviceutil \
+    ctstestrunner \
+    ub-uiautomator \
+    ShortcutManagerTestUtils
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher3/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/backup/launcher3/AndroidManifest.xml
new file mode 100644
index 0000000..82dc28f
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher3/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?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.shortcut.backup.launcher3">
+
+    <application android:allowBackup="false">
+        <activity android:name="MainActivity" android:exported="true" >
+            <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>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.content.pm.cts.shortcut.backup.launcher3"  />
+</manifest>
+
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher3/src/android/content/pm/cts/shortcut/backup/launcher3/MainActivity.java b/hostsidetests/shortcuts/deviceside/backup/launcher3/src/android/content/pm/cts/shortcut/backup/launcher3/MainActivity.java
new file mode 100644
index 0000000..064a881
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher3/src/android/content/pm/cts/shortcut/backup/launcher3/MainActivity.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.shortcut.backup.launcher3;
+
+import android.app.Activity;
+
+public class MainActivity extends Activity {
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher3/src/android/content/pm/cts/shortcut/backup/launcher3/ShortcutManagerPostBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/launcher3/src/android/content/pm/cts/shortcut/backup/launcher3/ShortcutManagerPostBackupTest.java
new file mode 100644
index 0000000..3945a2b
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher3/src/android/content/pm/cts/shortcut/backup/launcher3/ShortcutManagerPostBackupTest.java
@@ -0,0 +1,44 @@
+/*
+ * 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.shortcut.backup.launcher3;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+
+import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
+
+public class ShortcutManagerPostBackupTest extends ShortcutManagerDeviceTestBase {
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        setAsDefaultLauncher(MainActivity.class);
+    }
+
+    public void testWithUninstall_afterAppRestore() {
+        assertWith(getPackageShortcuts(ShortcutManagerPreBackupTest.PUBLISHER1_PKG))
+                .haveIds("ms1", "ms2")
+                .areAllEnabled()
+                .areAllNotPinned();
+
+        assertWith(getPackageShortcuts(ShortcutManagerPreBackupTest.PUBLISHER2_PKG))
+                .haveIds("ms1", "ms2")
+                .areAllEnabled();
+
+        assertWith(getPackageShortcuts(ShortcutManagerPreBackupTest.PUBLISHER3_PKG))
+                .haveIds("ms1", "ms2")
+                .areAllEnabled();
+    }
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher3/src/android/content/pm/cts/shortcut/backup/launcher3/ShortcutManagerPreBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/launcher3/src/android/content/pm/cts/shortcut/backup/launcher3/ShortcutManagerPreBackupTest.java
new file mode 100644
index 0000000..2638df3
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher3/src/android/content/pm/cts/shortcut/backup/launcher3/ShortcutManagerPreBackupTest.java
@@ -0,0 +1,45 @@
+/*
+ * 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.shortcut.backup.launcher3;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+
+import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+@SmallTest
+public class ShortcutManagerPreBackupTest extends ShortcutManagerDeviceTestBase {
+    static final String PUBLISHER1_PKG =
+            "android.content.pm.cts.shortcut.backup.publisher1";
+    static final String PUBLISHER2_PKG =
+            "android.content.pm.cts.shortcut.backup.publisher2";
+    static final String PUBLISHER3_PKG =
+            "android.content.pm.cts.shortcut.backup.publisher3";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        setAsDefaultLauncher(MainActivity.class);
+    }
+
+    public void testPreBackup() {
+        getLauncherApps().pinShortcuts(PUBLISHER1_PKG, list("s3", "ms1", "ms2"), getUserHandle());
+        getLauncherApps().pinShortcuts(PUBLISHER2_PKG, list("s1", "s3", "ms2"), getUserHandle());
+        getLauncherApps().pinShortcuts(PUBLISHER3_PKG, list("s1", "s3", "ms1"),
+                getUserHandle());
+    }
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher1/Android.mk b/hostsidetests/shortcuts/deviceside/backup/publisher1/Android.mk
new file mode 100644
index 0000000..2ac759a
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher1/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 := CtsShortcutBackupPublisher1
+
+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-minus-junit4 \
+    ctsdeviceutil \
+    ctstestrunner \
+    ub-uiautomator \
+    ShortcutManagerTestUtils
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher1/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/backup/publisher1/AndroidManifest.xml
new file mode 100644
index 0000000..3d23c72
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher1/AndroidManifest.xml
@@ -0,0 +1,44 @@
+<?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.shortcut.backup.publisher1">
+
+    <application>
+        <activity android:name="MainActivity" android:exported="true" >
+            <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>
+            <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts"/>
+        </activity>
+        <activity-alias android:name="MainActivity2"
+            android:targetActivity="android.content.pm.cts.shortcut.backup.publisher1.MainActivity"
+            android:exported="true" >
+            <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.shortcut.backup.publisher1"  />
+</manifest>
+
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher1/res/drawable-nodpi/black_16x16.png b/hostsidetests/shortcuts/deviceside/backup/publisher1/res/drawable-nodpi/black_16x16.png
new file mode 100644
index 0000000..a26da5c
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher1/res/drawable-nodpi/black_16x16.png
Binary files differ
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher1/res/drawable-nodpi/black_16x64.png b/hostsidetests/shortcuts/deviceside/backup/publisher1/res/drawable-nodpi/black_16x64.png
new file mode 100644
index 0000000..ed049fa
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher1/res/drawable-nodpi/black_16x64.png
Binary files differ
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher1/res/drawable-nodpi/black_64x16.png b/hostsidetests/shortcuts/deviceside/backup/publisher1/res/drawable-nodpi/black_64x16.png
new file mode 100644
index 0000000..a0983c7
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher1/res/drawable-nodpi/black_64x16.png
Binary files differ
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher1/res/values/strings.xml b/hostsidetests/shortcuts/deviceside/backup/publisher1/res/values/strings.xml
new file mode 100644
index 0000000..068c991
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher1/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/hostsidetests/shortcuts/deviceside/backup/publisher1/res/xml/shortcuts.xml b/hostsidetests/shortcuts/deviceside/backup/publisher1/res/xml/shortcuts.xml
new file mode 100644
index 0000000..1b4661b
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher1/res/xml/shortcuts.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="ms1"
+        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="ms2"
+        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/hostsidetests/shortcuts/deviceside/backup/publisher1/src/android/content/pm/cts/shortcut/backup/publisher1/MainActivity.java b/hostsidetests/shortcuts/deviceside/backup/publisher1/src/android/content/pm/cts/shortcut/backup/publisher1/MainActivity.java
new file mode 100644
index 0000000..95c75e6
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher1/src/android/content/pm/cts/shortcut/backup/publisher1/MainActivity.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.shortcut.backup.publisher1;
+
+import android.app.Activity;
+
+public class MainActivity extends Activity {
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher1/src/android/content/pm/cts/shortcut/backup/publisher1/ShortcutManagerPostBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/publisher1/src/android/content/pm/cts/shortcut/backup/publisher1/ShortcutManagerPostBackupTest.java
new file mode 100644
index 0000000..2129482
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher1/src/android/content/pm/cts/shortcut/backup/publisher1/ShortcutManagerPostBackupTest.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.content.pm.cts.shortcut.backup.publisher1;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+
+import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
+
+public class ShortcutManagerPostBackupTest extends ShortcutManagerDeviceTestBase {
+    public void testWithUninstall() {
+        assertWith(getManager().getDynamicShortcuts())
+                .isEmpty();
+
+        assertWith(getManager().getPinnedShortcuts())
+                .haveIds("s1", "s3", "ms1", "ms2")
+                .areAllEnabled();
+
+        assertWith(getManager().getManifestShortcuts())
+                .haveIds("ms1", "ms2")
+                .areAllPinned()
+                .areAllEnabled();
+    }
+
+    public void testWithNoUninstall() {
+        // Ideally this shouldn't be cleared, but for now it will be.
+//        assertWith(getManager().getDynamicShortcuts())
+//                .isEmpty();
+
+        assertWith(getManager().getPinnedShortcuts())
+                .isEmpty();
+
+        // Should still have the manifest shortcuts.
+        assertWith(getManager().getManifestShortcuts())
+                .haveIds("ms1", "ms2")
+                .areAllEnabled();
+    }
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher1/src/android/content/pm/cts/shortcut/backup/publisher1/ShortcutManagerPreBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/publisher1/src/android/content/pm/cts/shortcut/backup/publisher1/ShortcutManagerPreBackupTest.java
new file mode 100644
index 0000000..22b63e7
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher1/src/android/content/pm/cts/shortcut/backup/publisher1/ShortcutManagerPreBackupTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.shortcut.backup.publisher1;
+
+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.makePersistableBundle;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.set;
+
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.Icon;
+import android.test.suitebuilder.annotation.SmallTest;
+
+@SmallTest
+public class ShortcutManagerPreBackupTest extends ShortcutManagerDeviceTestBase {
+    public void testPreBackup() {
+        final Icon icon1 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+                getContext().getResources(), R.drawable.black_16x64));
+        final Icon icon3 = Icon.createWithResource(getContext(), R.drawable.black_64x16);
+
+        final ShortcutInfo s1 = new ShortcutInfo.Builder(getContext(), "s1")
+                .setShortLabel("shortlabel1")
+                .setLongLabel("longlabel1")
+                .setIcon(icon1)
+                .setActivity(getActivity("MainActivity"))
+                .setDisabledMessage("disabledmessage1")
+                .setIntents(new Intent[]{new Intent("view").putExtra("k1", "v1")})
+                .setExtras(makePersistableBundle("ek1", "ev1"))
+                .setCategories(set("cat1"))
+                .build();
+
+        final ShortcutInfo s2 = new ShortcutInfo.Builder(getContext(), "s2")
+                .setShortLabel("shortlabel2")
+                .setActivity(getActivity("MainActivity2"))
+                .setIntents(new Intent[]{new Intent("main")})
+                .build();
+
+        final ShortcutInfo s3 = new ShortcutInfo.Builder(getContext(), "s3")
+                .setShortLabel("shortlabel2")
+                .setIcon(icon3)
+                .setActivity(getActivity("MainActivity2"))
+                .setIntents(new Intent[]{new Intent("main")})
+                .build();
+
+        assertTrue(getManager().setDynamicShortcuts(list(s1, s2, s3)));
+
+        assertWith(getManager().getDynamicShortcuts())
+                .haveIds("s1", "s2", "s3")
+                .areAllNotPinned();
+
+        assertWith(getManager().getManifestShortcuts())
+                .haveIds("ms1", "ms2")
+                .areAllNotPinned();
+   }
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher2/Android.mk b/hostsidetests/shortcuts/deviceside/backup/publisher2/Android.mk
new file mode 100644
index 0000000..62ad16e
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher2/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 := CtsShortcutBackupPublisher2
+
+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-minus-junit4 \
+    ctsdeviceutil \
+    ctstestrunner \
+    ub-uiautomator \
+    ShortcutManagerTestUtils
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher2/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/backup/publisher2/AndroidManifest.xml
new file mode 100644
index 0000000..c3271b4
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher2/AndroidManifest.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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.content.pm.cts.shortcut.backup.publisher2">
+
+    <application>
+        <activity android:name="MainActivity" android:exported="true" >
+            <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>
+            <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts"/>
+        </activity>
+        <activity-alias android:name="MainActivity2"
+            android:targetActivity="android.content.pm.cts.shortcut.backup.publisher2.MainActivity"
+            android:exported="true" >
+            <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>
+            <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts_b"/>
+        </activity-alias>
+    </application>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.content.pm.cts.shortcut.backup.publisher2"  />
+</manifest>
+
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher2/res/drawable-nodpi/black_16x16.png b/hostsidetests/shortcuts/deviceside/backup/publisher2/res/drawable-nodpi/black_16x16.png
new file mode 100644
index 0000000..a26da5c
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher2/res/drawable-nodpi/black_16x16.png
Binary files differ
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher2/res/drawable-nodpi/black_16x64.png b/hostsidetests/shortcuts/deviceside/backup/publisher2/res/drawable-nodpi/black_16x64.png
new file mode 100644
index 0000000..ed049fa
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher2/res/drawable-nodpi/black_16x64.png
Binary files differ
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher2/res/drawable-nodpi/black_64x16.png b/hostsidetests/shortcuts/deviceside/backup/publisher2/res/drawable-nodpi/black_64x16.png
new file mode 100644
index 0000000..a0983c7
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher2/res/drawable-nodpi/black_64x16.png
Binary files differ
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher2/res/values/strings.xml b/hostsidetests/shortcuts/deviceside/backup/publisher2/res/values/strings.xml
new file mode 100644
index 0000000..068c991
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher2/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/hostsidetests/shortcuts/deviceside/backup/publisher2/res/xml/shortcuts.xml b/hostsidetests/shortcuts/deviceside/backup/publisher2/res/xml/shortcuts.xml
new file mode 100644
index 0000000..1b4661b
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher2/res/xml/shortcuts.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="ms1"
+        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="ms2"
+        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/hostsidetests/shortcuts/deviceside/backup/publisher2/res/xml/shortcuts_b.xml b/hostsidetests/shortcuts/deviceside/backup/publisher2/res/xml/shortcuts_b.xml
new file mode 100644
index 0000000..408e94d
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher2/res/xml/shortcuts_b.xml
@@ -0,0 +1,28 @@
+<?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/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>
+</shortcuts>
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher2/src/android/content/pm/cts/shortcut/backup/publisher2/MainActivity.java b/hostsidetests/shortcuts/deviceside/backup/publisher2/src/android/content/pm/cts/shortcut/backup/publisher2/MainActivity.java
new file mode 100644
index 0000000..a6ca8f1
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher2/src/android/content/pm/cts/shortcut/backup/publisher2/MainActivity.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.shortcut.backup.publisher2;
+
+import android.app.Activity;
+
+public class MainActivity extends Activity {
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher2/src/android/content/pm/cts/shortcut/backup/publisher2/ShortcutManagerPostBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/publisher2/src/android/content/pm/cts/shortcut/backup/publisher2/ShortcutManagerPostBackupTest.java
new file mode 100644
index 0000000..ce2b950
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher2/src/android/content/pm/cts/shortcut/backup/publisher2/ShortcutManagerPostBackupTest.java
@@ -0,0 +1,35 @@
+/*
+ * 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.shortcut.backup.publisher2;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+
+import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
+
+public class ShortcutManagerPostBackupTest extends ShortcutManagerDeviceTestBase {
+    public void testWithUninstall() {
+        assertWith(getManager().getDynamicShortcuts())
+                .isEmpty();
+
+        assertWith(getManager().getPinnedShortcuts())
+                .haveIds("s1", "s2", "ms1")
+                .areAllEnabled();
+
+        assertWith(getManager().getManifestShortcuts())
+                .haveIds("ms1", "ms2")
+                .areAllEnabled();
+    }
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher2/src/android/content/pm/cts/shortcut/backup/publisher2/ShortcutManagerPreBackup2Test.java b/hostsidetests/shortcuts/deviceside/backup/publisher2/src/android/content/pm/cts/shortcut/backup/publisher2/ShortcutManagerPreBackup2Test.java
new file mode 100644
index 0000000..d307c57
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher2/src/android/content/pm/cts/shortcut/backup/publisher2/ShortcutManagerPreBackup2Test.java
@@ -0,0 +1,59 @@
+/*
+ * 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.shortcut.backup.publisher2;
+
+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 android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+@SmallTest
+public class ShortcutManagerPreBackup2Test extends ShortcutManagerDeviceTestBase {
+    public void testPreBackup() {
+        getManager().removeDynamicShortcuts(list("s2"));
+        getManager().disableShortcuts(list("s3"));
+
+        getContext().getPackageManager().setComponentEnabledSetting(
+                new ComponentName(getContext(), MainActivity.class),
+                PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+                PackageManager.DONT_KILL_APP);
+
+        retryUntil(() -> getManager().getManifestShortcuts().size() == 1,
+                "Manifest shortcuts didn't update");
+
+        assertWith(getManager().getDynamicShortcuts())
+                .haveIds("s1")
+                .areAllEnabled();
+
+        assertWith(getManager().getManifestShortcuts())
+                .haveIds("ms1")
+                .areAllEnabled();
+
+        assertWith(getManager().getPinnedShortcuts())
+                .haveIds("s1", "s2", "s3", "ms1", "ms2")
+                .selectByIds("s1", "s2", "ms1")
+                .areAllEnabled()
+
+                .revertToOriginalList()
+                .selectByIds("s3", "ms2")
+                .areAllDisabled();
+
+   }
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher2/src/android/content/pm/cts/shortcut/backup/publisher2/ShortcutManagerPreBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/publisher2/src/android/content/pm/cts/shortcut/backup/publisher2/ShortcutManagerPreBackupTest.java
new file mode 100644
index 0000000..3d28145
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher2/src/android/content/pm/cts/shortcut/backup/publisher2/ShortcutManagerPreBackupTest.java
@@ -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.
+ */
+package android.content.pm.cts.shortcut.backup.publisher2;
+
+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.makePersistableBundle;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.set;
+
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.Icon;
+import android.test.suitebuilder.annotation.SmallTest;
+
+@SmallTest
+public class ShortcutManagerPreBackupTest extends ShortcutManagerDeviceTestBase {
+    public void testPreBackup() {
+        final Icon icon1 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+                getContext().getResources(), R.drawable.black_16x64));
+        final Icon icon3 = Icon.createWithResource(getContext(), R.drawable.black_64x16);
+
+        final ShortcutInfo s1 = new ShortcutInfo.Builder(getContext(), "s1")
+                .setShortLabel("shortlabel1")
+                .setLongLabel("longlabel1")
+                .setIcon(icon1)
+                .setActivity(getActivity("MainActivity2"))
+                .setDisabledMessage("disabledmessage1")
+                .setIntents(new Intent[]{new Intent("view").putExtra("k1", "v1")})
+                .setExtras(makePersistableBundle("ek1", "ev1"))
+                .setCategories(set("cat1"))
+                .build();
+
+        final ShortcutInfo s2 = new ShortcutInfo.Builder(getContext(), "s2")
+                .setShortLabel("shortlabel2")
+                .setActivity(getActivity("MainActivity2"))
+                .setIntents(new Intent[]{new Intent("main")})
+                .build();
+
+        final ShortcutInfo s3 = new ShortcutInfo.Builder(getContext(), "s3")
+                .setShortLabel("shortlabel2")
+                .setIcon(icon3)
+                .setActivity(getActivity("MainActivity2"))
+                .setIntents(new Intent[]{new Intent("main")})
+                .build();
+
+        assertTrue(getManager().setDynamicShortcuts(list(s1, s2, s3)));
+
+        assertWith(getManager().getDynamicShortcuts())
+                .haveIds("s1", "s2", "s3")
+                .areAllNotPinned();
+
+        assertWith(getManager().getManifestShortcuts())
+                .haveIds("ms1", "ms2")
+                .areAllNotPinned()
+                // Note MainActivity2 have manifest shortcuts too, but because of the ID overlap,
+                // they're ignored at this point.
+                .areAllWithActivity(getActivity("MainActivity"));
+   }
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher3/Android.mk b/hostsidetests/shortcuts/deviceside/backup/publisher3/Android.mk
new file mode 100644
index 0000000..049691d
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher3/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 := CtsShortcutBackupPublisher3
+
+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-minus-junit4 \
+    ctsdeviceutil \
+    ctstestrunner \
+    ub-uiautomator \
+    ShortcutManagerTestUtils
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher3/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/backup/publisher3/AndroidManifest.xml
new file mode 100644
index 0000000..5e03ff8
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher3/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?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.shortcut.backup.publisher3">
+
+    <application android:allowBackup="false">
+        <activity android:name="MainActivity" android:exported="true" >
+            <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>
+            <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts"/>
+        </activity>
+    </application>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.content.pm.cts.shortcut.backup.publisher3"  />
+</manifest>
+
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher3/res/drawable-nodpi/black_16x16.png b/hostsidetests/shortcuts/deviceside/backup/publisher3/res/drawable-nodpi/black_16x16.png
new file mode 100644
index 0000000..a26da5c
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher3/res/drawable-nodpi/black_16x16.png
Binary files differ
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher3/res/drawable-nodpi/black_16x64.png b/hostsidetests/shortcuts/deviceside/backup/publisher3/res/drawable-nodpi/black_16x64.png
new file mode 100644
index 0000000..ed049fa
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher3/res/drawable-nodpi/black_16x64.png
Binary files differ
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher3/res/drawable-nodpi/black_64x16.png b/hostsidetests/shortcuts/deviceside/backup/publisher3/res/drawable-nodpi/black_64x16.png
new file mode 100644
index 0000000..a0983c7
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher3/res/drawable-nodpi/black_64x16.png
Binary files differ
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher3/res/values/strings.xml b/hostsidetests/shortcuts/deviceside/backup/publisher3/res/values/strings.xml
new file mode 100644
index 0000000..068c991
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher3/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/hostsidetests/shortcuts/deviceside/backup/publisher3/res/xml/shortcuts.xml b/hostsidetests/shortcuts/deviceside/backup/publisher3/res/xml/shortcuts.xml
new file mode 100644
index 0000000..1b4661b
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher3/res/xml/shortcuts.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="ms1"
+        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="ms2"
+        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/hostsidetests/shortcuts/deviceside/backup/publisher3/src/android/content/pm/cts/shortcut/backup/publisher3/MainActivity.java b/hostsidetests/shortcuts/deviceside/backup/publisher3/src/android/content/pm/cts/shortcut/backup/publisher3/MainActivity.java
new file mode 100644
index 0000000..ad66bd9
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher3/src/android/content/pm/cts/shortcut/backup/publisher3/MainActivity.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.shortcut.backup.publisher3;
+
+import android.app.Activity;
+
+public class MainActivity extends Activity {
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher3/src/android/content/pm/cts/shortcut/backup/publisher3/ShortcutManagerPostBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/publisher3/src/android/content/pm/cts/shortcut/backup/publisher3/ShortcutManagerPostBackupTest.java
new file mode 100644
index 0000000..a36cc66
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher3/src/android/content/pm/cts/shortcut/backup/publisher3/ShortcutManagerPostBackupTest.java
@@ -0,0 +1,32 @@
+/*
+ * 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.shortcut.backup.publisher3;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+
+import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
+
+public class ShortcutManagerPostBackupTest extends ShortcutManagerDeviceTestBase {
+    public void testWithUninstall() {
+        // backup = false, so no pinned shortcuts should be restored.
+        assertWith(getManager().getPinnedShortcuts())
+                .isEmpty();
+
+        assertWith(getManager().getManifestShortcuts())
+                .haveIds("ms1", "ms2")
+                .areAllNotPinned();
+    }
+}
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher3/src/android/content/pm/cts/shortcut/backup/publisher3/ShortcutManagerPreBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/publisher3/src/android/content/pm/cts/shortcut/backup/publisher3/ShortcutManagerPreBackupTest.java
new file mode 100644
index 0000000..01166e7
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher3/src/android/content/pm/cts/shortcut/backup/publisher3/ShortcutManagerPreBackupTest.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.shortcut.backup.publisher3;
+
+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.makePersistableBundle;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.set;
+
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.Icon;
+import android.test.suitebuilder.annotation.SmallTest;
+
+@SmallTest
+public class ShortcutManagerPreBackupTest extends ShortcutManagerDeviceTestBase {
+    public void testPreBackup() {
+        final Icon icon1 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+                getContext().getResources(), R.drawable.black_16x64));
+        final Icon icon3 = Icon.createWithResource(getContext(), R.drawable.black_64x16);
+
+        final ShortcutInfo s1 = new ShortcutInfo.Builder(getContext(), "s1")
+                .setShortLabel("shortlabel1")
+                .setLongLabel("longlabel1")
+                .setIcon(icon1)
+                .setDisabledMessage("disabledmessage1")
+                .setIntents(new Intent[]{new Intent("view").putExtra("k1", "v1")})
+                .setExtras(makePersistableBundle("ek1", "ev1"))
+                .setCategories(set("cat1"))
+                .build();
+
+        final ShortcutInfo s2 = new ShortcutInfo.Builder(getContext(), "s2")
+                .setShortLabel("shortlabel2")
+                .setIntents(new Intent[]{new Intent("main")})
+                .build();
+
+        final ShortcutInfo s3 = new ShortcutInfo.Builder(getContext(), "s3")
+                .setShortLabel("shortlabel2")
+                .setIcon(icon3)
+                .setIntents(new Intent[]{new Intent("main")})
+                .build();
+
+        assertTrue(getManager().setDynamicShortcuts(list(s1, s2, s3)));
+
+        assertWith(getManager().getDynamicShortcuts())
+                .haveIds("s1", "s2", "s3")
+                .areAllNotPinned();
+
+        assertWith(getManager().getManifestShortcuts())
+                .haveIds("ms1", "ms2")
+                .areAllNotPinned();
+   }
+}
diff --git a/hostsidetests/shortcuts/deviceside/common/src/android/content/pm/cts/shortcut/device/common/ShortcutManagerDeviceTestBase.java b/hostsidetests/shortcuts/deviceside/common/src/android/content/pm/cts/shortcut/device/common/ShortcutManagerDeviceTestBase.java
new file mode 100644
index 0000000..f3fd238
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/common/src/android/content/pm/cts/shortcut/device/common/ShortcutManagerDeviceTestBase.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.shortcut.device.common;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.getDefaultLauncher;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.setDefaultLauncher;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.LauncherApps;
+import android.content.pm.LauncherApps.ShortcutQuery;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.os.UserHandle;
+import android.test.InstrumentationTestCase;
+import android.text.TextUtils;
+
+import java.util.List;
+
+/**
+ * Base class for device side tests for the host test.
+ */
+public abstract class ShortcutManagerDeviceTestBase extends InstrumentationTestCase {
+    private ShortcutManager mManager;
+    private LauncherApps mLauncherApps;
+
+    private String mOriginalLauncher;
+
+    protected Context getContext() {
+        return getInstrumentation().getTargetContext();
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mOriginalLauncher = getDefaultLauncher(getInstrumentation());
+
+        mManager = getContext().getSystemService(ShortcutManager.class);
+        mLauncherApps = getContext().getSystemService(LauncherApps.class);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (!TextUtils.isEmpty(mOriginalLauncher)) {
+            setDefaultLauncher(getInstrumentation(), mOriginalLauncher);
+        }
+
+        super.tearDown();
+    }
+
+    protected ShortcutManager getManager() {
+        return mManager;
+    }
+
+    protected LauncherApps getLauncherApps() {
+        return mLauncherApps;
+    }
+
+    protected UserHandle getUserHandle() {
+        return android.os.Process.myUserHandle();
+    }
+
+    protected void setAsDefaultLauncher(Class<?> clazz) {
+        setDefaultLauncher(getInstrumentation(),
+                getContext().getPackageName() + "/" + clazz.getName());
+    }
+
+    protected Drawable getIconAsLauncher(String packageName, String shortcutId) {
+        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());
+
+        return getLauncherApps().getShortcutIconDrawable(found.get(0), 0);
+    }
+
+    protected void assertIconDimensions(String packageName,
+            String shortcutId, Icon expectedIcon) {
+        final Drawable actual = getIconAsLauncher(packageName, shortcutId);
+        if (actual == null && expectedIcon == null) {
+            return; // okay
+        }
+        final Drawable expected = expectedIcon.loadDrawable(getContext());
+        assertEquals(expected.getIntrinsicWidth(), actual.getIntrinsicWidth());
+        assertEquals(expected.getIntrinsicHeight(), actual.getIntrinsicHeight());
+    }
+
+    public ComponentName getActivity(String className) {
+        return new ComponentName(getContext(), getContext().getPackageName() + "." + className);
+    }
+
+    protected List<ShortcutInfo> getPackageShortcuts(String packageName) {
+        final ShortcutQuery q = new ShortcutQuery()
+                .setQueryFlags(ShortcutQuery.FLAG_MATCH_DYNAMIC
+                        | ShortcutQuery.FLAG_MATCH_MANIFEST
+                        | ShortcutQuery.FLAG_MATCH_PINNED)
+                .setPackage(packageName);
+        return getLauncherApps().getShortcuts(q, getUserHandle());
+    }
+}
diff --git a/hostsidetests/shortcuts/deviceside/multiuser/Android.mk b/hostsidetests/shortcuts/deviceside/multiuser/Android.mk
new file mode 100644
index 0000000..2897e8c
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/multiuser/Android.mk
@@ -0,0 +1,44 @@
+# 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.
+
+# We build two APKs from the same source files, each with a different set of resources.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_PACKAGE_NAME := CtsShortcutMultiuserTest
+
+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-minus-junit4 \
+    ctsdeviceutil \
+    ctstestrunner \
+    ub-uiautomator \
+    ShortcutManagerTestUtils
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/multiuser/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/multiuser/AndroidManifest.xml
new file mode 100644
index 0000000..3fbed5f
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/multiuser/AndroidManifest.xml
@@ -0,0 +1,42 @@
+<?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.shortcut.multiuser">
+
+    <application>
+        <activity android:name="Launcher" android:exported="true" >
+            <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 android:name="MainActivity" android:exported="true" >
+            <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>
+    </application>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.content.pm.cts.shortcut.multiuser"  />
+</manifest>
+
diff --git a/hostsidetests/shortcuts/deviceside/multiuser/src/android/content/pm/cts/shortcut/multiuser/Consts.java b/hostsidetests/shortcuts/deviceside/multiuser/src/android/content/pm/cts/shortcut/multiuser/Consts.java
new file mode 100644
index 0000000..fb15f93
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/multiuser/src/android/content/pm/cts/shortcut/multiuser/Consts.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.content.pm.cts.shortcut.multiuser;
+
+public class Consts {
+    private Consts() {
+    }
+
+    public static final String TAG = "ShortcutMultiuserCTS";
+}
diff --git a/hostsidetests/shortcuts/deviceside/multiuser/src/android/content/pm/cts/shortcut/multiuser/Launcher.java b/hostsidetests/shortcuts/deviceside/multiuser/src/android/content/pm/cts/shortcut/multiuser/Launcher.java
new file mode 100644
index 0000000..c860523
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/multiuser/src/android/content/pm/cts/shortcut/multiuser/Launcher.java
@@ -0,0 +1,29 @@
+/*
+ * 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.shortcut.multiuser;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.setDefaultLauncher;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.Context;
+
+public class Launcher extends Activity {
+    public static void setAsDefaultLauncher(Instrumentation instrumentation, Context context) {
+        setDefaultLauncher(instrumentation,
+                context.getPackageName() + "/" + Launcher.class.getName());
+    }
+}
diff --git a/hostsidetests/shortcuts/deviceside/multiuser/src/android/content/pm/cts/shortcut/multiuser/MainActivity.java b/hostsidetests/shortcuts/deviceside/multiuser/src/android/content/pm/cts/shortcut/multiuser/MainActivity.java
new file mode 100644
index 0000000..76a081e
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/multiuser/src/android/content/pm/cts/shortcut/multiuser/MainActivity.java
@@ -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.
+ */
+package android.content.pm.cts.shortcut.multiuser;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class MainActivity extends Activity {
+    private static volatile long sLastCreateTime;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        sLastCreateTime = System.currentTimeMillis();
+
+        finish();
+    }
+
+    public static long getLastCreateTime() {
+        return sLastCreateTime;
+    }
+}
diff --git a/hostsidetests/shortcuts/deviceside/multiuser/src/android/content/pm/cts/shortcut/multiuser/ShortcutManagerManagedUserTest.java b/hostsidetests/shortcuts/deviceside/multiuser/src/android/content/pm/cts/shortcut/multiuser/ShortcutManagerManagedUserTest.java
new file mode 100644
index 0000000..0c5e662
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/multiuser/src/android/content/pm/cts/shortcut/multiuser/ShortcutManagerManagedUserTest.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.content.pm.cts.shortcut.multiuser;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.LauncherApps.ShortcutQuery;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+public class ShortcutManagerManagedUserTest extends ShortcutManagerDeviceTestBase {
+    public void test01_managedProfileNotStarted() {
+        Launcher.setAsDefaultLauncher(getInstrumentation(), getContext());
+
+        // Get user-0's handle.
+        final UserHandle userManaged = getManagedUser();
+
+        final ShortcutQuery q = new ShortcutQuery();
+
+        try {
+            getLauncherApps().getShortcuts(q, userManaged);
+            fail("Didn't throw SecurityException");
+        } catch (IllegalStateException e) {
+            assertTrue(e.getMessage().contains("locked or not running"));
+        }
+    }
+
+    public void test02_createShortuctsOnPrimaryUser() {
+        assertTrue(getManager().setDynamicShortcuts(list(
+                new ShortcutInfo.Builder(getContext(), "s1")
+                        .setShortLabel("label1")
+                        .setIntent(new Intent(Intent.ACTION_VIEW).setComponent(
+                                new ComponentName(getContext(), MainActivity.class))).build())));
+    }
+
+    public void test03_createShortuctsOnManagedProfile() {
+        assertTrue(getManager().setDynamicShortcuts(list(
+                new ShortcutInfo.Builder(getContext(), "s1")
+                        .setShortLabel("label2")
+                        .setIntent(new Intent(Intent.ACTION_VIEW).setComponent(
+                                new ComponentName(getContext(), MainActivity.class))).build())));
+    }
+
+    public void test04_getAndLaunch() {
+        Launcher.setAsDefaultLauncher(getInstrumentation(), getContext());
+
+        final UserHandle userMain = android.os.Process.myUserHandle();
+        final UserHandle userManaged = getManagedUser();
+
+        final ShortcutQuery q = new ShortcutQuery()
+                .setQueryFlags(ShortcutQuery.FLAG_MATCH_DYNAMIC)
+                .setPackage(getContext().getPackageName())
+                .setShortcutIds(list("s1"));
+        assertWith(getLauncherApps().getShortcuts(q, userMain))
+                .haveIds("s1")
+                .areAllDynamic()
+                .forShortcutWithId("s1", si -> {
+                    assertEquals("label1", si.getShortLabel());
+                    assertEquals(userMain, si.getUserHandle());
+                });
+        assertWith(getLauncherApps().getShortcuts(q, userManaged))
+                .haveIds("s1")
+                .areAllDynamic()
+                .forShortcutWithId("s1", si -> {
+                    assertEquals("label2", si.getShortLabel());
+                    assertEquals(userManaged, si.getUserHandle());
+                });
+
+        // Just call start and make sure they don't throw.
+        getLauncherApps().startShortcut(getContext().getPackageName(), "s1", null, null,
+                userMain);
+
+        // TODO Make sure the activity actually starts.
+        getLauncherApps().startShortcut(getContext().getPackageName(), "s1", null, null,
+                userManaged);
+    }
+
+    private UserHandle getManagedUser() {
+        for (UserHandle user : getContext().getSystemService(UserManager.class).getUserProfiles()) {
+            if (user.equals(android.os.Process.myUserHandle())) {
+                continue;
+            }
+            return user;
+        }
+        fail("Managed user not found");
+        return null;
+    }
+}
diff --git a/hostsidetests/shortcuts/deviceside/multiuser/src/android/content/pm/cts/shortcut/multiuser/ShortcutManagerSecondaryUserTest.java b/hostsidetests/shortcuts/deviceside/multiuser/src/android/content/pm/cts/shortcut/multiuser/ShortcutManagerSecondaryUserTest.java
new file mode 100644
index 0000000..618bbae
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/multiuser/src/android/content/pm/cts/shortcut/multiuser/ShortcutManagerSecondaryUserTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.shortcut.multiuser;
+
+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 android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.LauncherApps.ShortcutQuery;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
+import android.os.UserHandle;
+import android.util.DisplayMetrics;
+
+import java.lang.reflect.Method;
+import java.util.List;
+
+public class ShortcutManagerSecondaryUserTest extends ShortcutManagerDeviceTestBase {
+    public void testCreateAndStart() {
+        Launcher.setAsDefaultLauncher(getInstrumentation(), getContext());
+
+        // Publish a shortcut.
+        final UserHandle user = android.os.Process.myUserHandle();
+
+        assertTrue(getManager().setDynamicShortcuts(list(
+                new ShortcutInfo.Builder(getContext(), "s1")
+                    .setShortLabel("label")
+                    .setIntent(new Intent(Intent.ACTION_VIEW).setComponent(
+                            new ComponentName(getContext(), MainActivity.class))).build())));
+
+        // Retrieve as a launcher.
+        final ShortcutQuery q = new ShortcutQuery()
+                .setQueryFlags(ShortcutQuery.FLAG_MATCH_DYNAMIC)
+                .setPackage(getContext().getPackageName())
+                .setShortcutIds(list("s1"));
+        final List<ShortcutInfo> list = getLauncherApps().getShortcuts(q, user);
+        assertWith(list)
+                .haveIds("s1")
+                .areAllDynamic()
+                .forShortcutWithId("s1", si -> {
+                    assertEquals(user, si.getUserHandle());
+                });
+
+        final ShortcutInfo s1 = list.get(0);
+
+        // Just make sure they don't throw SecurityException.
+        getLauncherApps().getShortcutIconDrawable(s1, DisplayMetrics.DENSITY_DEFAULT);
+        getLauncherApps().getShortcutBadgedIconDrawable(s1, DisplayMetrics.DENSITY_DEFAULT);
+
+        final long now = System.currentTimeMillis();
+
+        // Start it.
+        getLauncherApps().startShortcut(s1, null, null);
+
+        retryUntil(() -> MainActivity.getLastCreateTime() >= now, "Activity not started");
+    }
+
+    public void testDifferentUserNotAccessible() throws Exception {
+        Launcher.setAsDefaultLauncher(getInstrumentation(), getContext());
+
+        // Get user-0's handle.
+        final UserHandle user0 = getUser0Handle();
+
+        final ShortcutQuery q = new ShortcutQuery();
+
+        try {
+            getLauncherApps().getShortcuts(q, user0);
+            fail("Didn't throw SecurityException");
+        } catch (SecurityException e) {
+            assertTrue(e.getMessage().contains("unrelated profile"));
+        }
+    }
+
+    private static UserHandle getUser0Handle() throws Exception {
+        Method of = UserHandle.class.getMethod("of", int.class);
+
+        return (UserHandle) of.invoke(null, 0);
+    }
+}
diff --git a/hostsidetests/shortcuts/deviceside/upgrade/Android.mk b/hostsidetests/shortcuts/deviceside/upgrade/Android.mk
new file mode 100644
index 0000000..21b07a0
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/upgrade/Android.mk
@@ -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.
+
+# We build two APKs from the same source files, each with a different set of resources.
+
+# =================================
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_PACKAGE_NAME := CtsShortcutUpgradeVersion1
+
+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_RESOURCE_DIR :=  $(LOCAL_PATH)/version1/res
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    android-support-v4 \
+    mockito-target-minus-junit4 \
+    ctsdeviceutil \
+    ctstestrunner \
+    ub-uiautomator \
+    ShortcutManagerTestUtils
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
+
+# =============================
+
+include $(CLEAR_VARS)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_PACKAGE_NAME := CtsShortcutUpgradeVersion2
+
+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_RESOURCE_DIR :=  $(LOCAL_PATH)/version2/res
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    android-support-v4 \
+    mockito-target-minus-junit4 \
+    ctsdeviceutil \
+    ctstestrunner \
+    ub-uiautomator \
+    ShortcutManagerTestUtils
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/shortcuts/deviceside/upgrade/AndroidManifest.xml b/hostsidetests/shortcuts/deviceside/upgrade/AndroidManifest.xml
new file mode 100644
index 0000000..1b88d5e
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/upgrade/AndroidManifest.xml
@@ -0,0 +1,42 @@
+<?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.shortcut.upgrade">
+
+    <application>
+        <activity android:name="Launcher" android:exported="true" >
+            <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 android:name="MainActivty" android:exported="true" >
+            <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>
+    </application>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.content.pm.cts.shortcut.upgrade"  />
+</manifest>
+
diff --git a/hostsidetests/shortcuts/deviceside/upgrade/src/android/content/pm/cts/shortcut/upgrade/Consts.java b/hostsidetests/shortcuts/deviceside/upgrade/src/android/content/pm/cts/shortcut/upgrade/Consts.java
new file mode 100644
index 0000000..c13e3d0
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/upgrade/src/android/content/pm/cts/shortcut/upgrade/Consts.java
@@ -0,0 +1,26 @@
+/*
+ * 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.shortcut.upgrade;
+
+public class Consts {
+    private Consts() {
+    }
+
+    public static final String TAG = "ShortcutUpgradeCTS";
+
+    public static String EXTRA_ICON_RES_ID =
+            "android.content.pm.cts.shortcut.upgrade.EXTRA_ICON_RES_ID";
+}
diff --git a/hostsidetests/shortcuts/deviceside/upgrade/src/android/content/pm/cts/shortcut/upgrade/Launcher.java b/hostsidetests/shortcuts/deviceside/upgrade/src/android/content/pm/cts/shortcut/upgrade/Launcher.java
new file mode 100644
index 0000000..21b0b03
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/upgrade/src/android/content/pm/cts/shortcut/upgrade/Launcher.java
@@ -0,0 +1,29 @@
+/*
+ * 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.shortcut.upgrade;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.setDefaultLauncher;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.Context;
+
+public class Launcher extends Activity {
+    public static void setAsDefaultLauncher(Instrumentation instrumentation, Context context) {
+        setDefaultLauncher(instrumentation,
+                context.getPackageName() + "/" + Launcher.class.getName());
+    }
+}
diff --git a/hostsidetests/shortcuts/deviceside/upgrade/src/android/content/pm/cts/shortcut/upgrade/MainActivity.java b/hostsidetests/shortcuts/deviceside/upgrade/src/android/content/pm/cts/shortcut/upgrade/MainActivity.java
new file mode 100644
index 0000000..51c7a76
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/upgrade/src/android/content/pm/cts/shortcut/upgrade/MainActivity.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.shortcut.upgrade;
+
+import android.app.Activity;
+
+public class MainActivity extends Activity {
+}
diff --git a/hostsidetests/shortcuts/deviceside/upgrade/src/android/content/pm/cts/shortcut/upgrade/ShortcutManagerPostUpgradeTest.java b/hostsidetests/shortcuts/deviceside/upgrade/src/android/content/pm/cts/shortcut/upgrade/ShortcutManagerPostUpgradeTest.java
new file mode 100644
index 0000000..d20992d
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/upgrade/src/android/content/pm/cts/shortcut/upgrade/ShortcutManagerPostUpgradeTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.cts.shortcut.upgrade;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+
+import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
+import android.graphics.drawable.Icon;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import junit.framework.AssertionFailedError;
+
+@SmallTest
+public class ShortcutManagerPostUpgradeTest extends ShortcutManagerDeviceTestBase {
+    public void testPostUpgrade() {
+        Log.i(Consts.TAG, "Post: ResIDs=" + R.drawable.black_32x32 + ", " + R.drawable.black_64x64);
+
+        // Get the shortcuts published by the "pre" apk.
+
+        // Check their original res IDs (stored in the extras) and make sure the res IDs are
+        // different now.
+        assertWith(getManager().getDynamicShortcuts())
+                .haveIds("s1", "s2")
+                .forShortcutWithId("s1", s -> {
+                    assertTrue(
+                            R.drawable.black_32x32 !=
+                            s.getExtras().getInt(Consts.EXTRA_ICON_RES_ID));
+                })
+                .forShortcutWithId("s2", s -> {
+                    assertTrue(
+                            R.drawable.black_64x64 !=
+                            s.getExtras().getInt(Consts.EXTRA_ICON_RES_ID));
+                });
+
+        // Next, actually fetch the icons as a launcher, and make sure the dimensions are correct.
+        final Icon icon1 = Icon.createWithResource(getContext(), R.drawable.black_32x32);
+        final Icon icon2 = Icon.createWithResource(getContext(), R.drawable.black_64x64);
+
+        // Set this package as a default launcher to access LauncherApps.
+        Launcher.setAsDefaultLauncher(getInstrumentation(), getContext());
+
+        // Check the published icons as a launcher.
+        assertIconDimensions(getContext().getPackageName(), "s1", icon1);
+        assertIconDimensions(getContext().getPackageName(), "s2", icon2);
+
+        // Paranoid: this should fail.
+        boolean notThrown = false;
+        try {
+            assertIconDimensions(getContext().getPackageName(), "s1", icon2);
+            notThrown = true;
+        } catch (AssertionFailedError expected) {
+            // okay
+        }
+        assertFalse(notThrown);
+    }
+}
diff --git a/hostsidetests/shortcuts/deviceside/upgrade/src/android/content/pm/cts/shortcut/upgrade/ShortcutManagerPreUpgradeTest.java b/hostsidetests/shortcuts/deviceside/upgrade/src/android/content/pm/cts/shortcut/upgrade/ShortcutManagerPreUpgradeTest.java
new file mode 100644
index 0000000..50c2f78
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/upgrade/src/android/content/pm/cts/shortcut/upgrade/ShortcutManagerPreUpgradeTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.shortcut.upgrade;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
+import android.graphics.drawable.Icon;
+import android.os.PersistableBundle;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import junit.framework.AssertionFailedError;
+
+@SmallTest
+public class ShortcutManagerPreUpgradeTest extends ShortcutManagerDeviceTestBase {
+    public void testPreUpgrade() {
+        Log.i(Consts.TAG, "Pre: ResIDs=" + R.drawable.black_32x32 + ", " + R.drawable.black_64x64);
+
+        // Publish shortcuts with drawable icons.
+        final Icon icon1 = Icon.createWithResource(getContext(), R.drawable.black_32x32);
+        final Icon icon2 = Icon.createWithResource(getContext(), R.drawable.black_64x64);
+
+        // Store the original resource ID in the extras.
+        final PersistableBundle b1 = new PersistableBundle();
+        b1.putInt(Consts.EXTRA_ICON_RES_ID, R.drawable.black_32x32);
+        final ShortcutInfo s1 = new ShortcutInfo.Builder(getContext(), "s1")
+                .setShortLabel("shortlabel1")
+                .setIcon(icon1)
+                .setIntents(new Intent[]{new Intent(Intent.ACTION_VIEW)})
+                .setExtras(b1)
+                .build();
+
+        final PersistableBundle b2 = new PersistableBundle();
+        b2.putInt(Consts.EXTRA_ICON_RES_ID, R.drawable.black_64x64);
+        final ShortcutInfo s2 = new ShortcutInfo.Builder(getContext(), "s2")
+                .setShortLabel("shortlabel2")
+                .setIcon(icon2)
+                .setIntents(new Intent[]{new Intent(Intent.ACTION_VIEW)})
+                .setExtras(b2)
+                .build();
+
+        assertTrue(getManager().setDynamicShortcuts(list(s1, s2)));
+
+        // Set this package as a default launcher to access LauncherApps.
+        Launcher.setAsDefaultLauncher(getInstrumentation(), getContext());
+
+        // Check the published icons as a launcher.
+        assertIconDimensions(getContext().getPackageName(), "s1", icon1);
+        assertIconDimensions(getContext().getPackageName(), "s2", icon2);
+
+        // Paranoid: this should fail.
+        boolean notThrown = false;
+        try {
+            assertIconDimensions(getContext().getPackageName(), "s1", icon2);
+            notThrown = true;
+        } catch (AssertionFailedError expected) {
+            // okay
+        }
+        assertFalse(notThrown);
+    }
+}
diff --git a/hostsidetests/shortcuts/deviceside/upgrade/version1/res/drawable-nodpi/black_16x16.png b/hostsidetests/shortcuts/deviceside/upgrade/version1/res/drawable-nodpi/black_16x16.png
new file mode 100644
index 0000000..a26da5c
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/upgrade/version1/res/drawable-nodpi/black_16x16.png
Binary files differ
diff --git a/hostsidetests/shortcuts/deviceside/upgrade/version1/res/drawable-nodpi/black_16x64.png b/hostsidetests/shortcuts/deviceside/upgrade/version1/res/drawable-nodpi/black_16x64.png
new file mode 100644
index 0000000..ed049fa
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/upgrade/version1/res/drawable-nodpi/black_16x64.png
Binary files differ
diff --git a/hostsidetests/shortcuts/deviceside/upgrade/version1/res/drawable-nodpi/black_32x32.png b/hostsidetests/shortcuts/deviceside/upgrade/version1/res/drawable-nodpi/black_32x32.png
new file mode 100644
index 0000000..a1e25c1
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/upgrade/version1/res/drawable-nodpi/black_32x32.png
Binary files differ
diff --git a/hostsidetests/shortcuts/deviceside/upgrade/version1/res/drawable-nodpi/black_64x16.png b/hostsidetests/shortcuts/deviceside/upgrade/version1/res/drawable-nodpi/black_64x16.png
new file mode 100644
index 0000000..a0983c7
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/upgrade/version1/res/drawable-nodpi/black_64x16.png
Binary files differ
diff --git a/hostsidetests/shortcuts/deviceside/upgrade/version1/res/drawable-nodpi/black_64x64.png b/hostsidetests/shortcuts/deviceside/upgrade/version1/res/drawable-nodpi/black_64x64.png
new file mode 100644
index 0000000..7cc9373
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/upgrade/version1/res/drawable-nodpi/black_64x64.png
Binary files differ
diff --git a/hostsidetests/shortcuts/deviceside/upgrade/version2/res/drawable-nodpi/black_32x32.png b/hostsidetests/shortcuts/deviceside/upgrade/version2/res/drawable-nodpi/black_32x32.png
new file mode 100644
index 0000000..a1e25c1
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/upgrade/version2/res/drawable-nodpi/black_32x32.png
Binary files differ
diff --git a/hostsidetests/shortcuts/deviceside/upgrade/version2/res/drawable-nodpi/black_64x64.png b/hostsidetests/shortcuts/deviceside/upgrade/version2/res/drawable-nodpi/black_64x64.png
new file mode 100644
index 0000000..7cc9373
--- /dev/null
+++ b/hostsidetests/shortcuts/deviceside/upgrade/version2/res/drawable-nodpi/black_64x64.png
Binary files differ
diff --git a/hostsidetests/shortcuts/hostside/Android.mk b/hostsidetests/shortcuts/hostside/Android.mk
new file mode 100644
index 0000000..56b2e60
--- /dev/null
+++ b/hostsidetests/shortcuts/hostside/Android.mk
@@ -0,0 +1,37 @@
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := CtsShortcutHostTestCases
+
+LOCAL_CTS_TEST_PACKAGE := android.shortcutshostside
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := tools-common-prebuilt cts-tradefed tradefed-prebuilt
+
+LOCAL_STATIC_JAVA_LIBRARIES := cts-migration-lib
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+include $(BUILD_CTS_HOST_JAVA_LIBRARY)
+
+# Build the test APKs using their own makefiles
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/shortcuts/hostside/AndroidTest.xml b/hostsidetests/shortcuts/hostside/AndroidTest.xml
new file mode 100644
index 0000000..13b9e14
--- /dev/null
+++ b/hostsidetests/shortcuts/hostside/AndroidTest.xml
@@ -0,0 +1,21 @@
+<?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 the CTS ShortcutManager host tests">
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsShortcutHostTestCases.jar" />
+        <option name="runtime-hint" value="20m" />
+    </test>
+</configuration>
diff --git a/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/BaseShortcutManagerHostTest.java b/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/BaseShortcutManagerHostTest.java
new file mode 100644
index 0000000..fbd344b
--- /dev/null
+++ b/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/BaseShortcutManagerHostTest.java
@@ -0,0 +1,280 @@
+/*
+ * 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.shortcuthost;
+
+import com.android.cts.migration.MigrationHelper;
+import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.ddmlib.testrunner.TestResult;
+import com.android.ddmlib.testrunner.TestResult.TestStatus;
+import com.android.ddmlib.testrunner.TestRunResult;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.CollectingTestListener;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Map;
+import java.util.regex.MatchResult;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.annotation.Nullable;
+
+abstract public class BaseShortcutManagerHostTest extends DeviceTestCase implements IBuildReceiver {
+    protected static final boolean DUMPSYS_IN_TEARDOWN = false; // DO NOT SUBMIT WITH TRUE
+
+    private static final String RUNNER = "android.support.test.runner.AndroidJUnitRunner";
+
+    private IBuildInfo mCtsBuild;
+
+    protected boolean mIsMultiuserSupported;
+    protected boolean mIsManagedUserSupported;
+
+    private ArrayList<Integer> mOriginalUsers;
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        assertNotNull(mCtsBuild);  // ensure build has been set before test is run.
+
+        mIsMultiuserSupported = getDevice().isMultiUserSupported();
+        if (!mIsMultiuserSupported) {
+            CLog.w("Multi user not supporeted");
+        }
+        mIsManagedUserSupported = getDevice().hasFeature("android.software.managed_users");
+        if (!mIsManagedUserSupported) {
+            CLog.w("Managed users not supporeted");
+        }
+
+        if (mIsMultiuserSupported) {
+            mOriginalUsers = new ArrayList<>(getDevice().listUsers());
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        removeTestUsers();
+        super.tearDown();
+    }
+
+    protected void dumpsys(String label) throws DeviceNotAvailableException {
+        CLog.w("dumpsys shortcuts #" + label);
+
+        CLog.w(getDevice().executeShellCommand("dumpsys shortcut"));
+    }
+
+    protected String executeShellCommandWithLog(String command) throws DeviceNotAvailableException {
+        CLog.i("Executing command: " + command);
+        final String output = getDevice().executeShellCommand(command);
+        CLog.i(output);
+        return output;
+    }
+
+    protected void clearShortcuts(String packageName, int userId) throws Exception {
+        assertContainsRegex("Success",
+                getDevice().executeShellCommand("cmd shortcut clear-shortcuts --user " + userId
+                        + " " + packageName));
+    }
+
+    protected void installAppAsUser(String appFileName, int userId) throws FileNotFoundException,
+            DeviceNotAvailableException {
+        CLog.i("Installing app " + appFileName + " for user " + userId);
+        String result = getDevice().installPackageForUser(
+                MigrationHelper.getTestFile(mCtsBuild, appFileName), true, true,
+                userId, "-t");
+        assertNull("Failed to install " + appFileName + " for user " + userId + ": " + result,
+                result);
+    }
+
+    protected int getPrimaryUserId() throws DeviceNotAvailableException {
+        return getDevice().getPrimaryUserId();
+    }
+
+    /** Returns true if the specified tests passed. Tests are run as given user. */
+    protected void runDeviceTestsAsUser(
+            String pkgName, @Nullable String testClassName, int userId)
+            throws DeviceNotAvailableException {
+        runDeviceTestsAsUser(pkgName, testClassName, null /*testMethodName*/, userId);
+    }
+
+    /** Returns true if the specified tests passed. Tests are run as given user. */
+    protected void runDeviceTestsAsUser(
+            String pkgName, @Nullable String testClassName, String testMethodName, int userId)
+            throws DeviceNotAvailableException {
+        Map<String, String> params = Collections.emptyMap();
+        runDeviceTestsAsUser(pkgName, testClassName, testMethodName, userId, params);
+    }
+
+    protected void runDeviceTestsAsUser(String pkgName, @Nullable String testClassName,
+            @Nullable String testMethodName, int userId,
+            Map<String, String> params) throws DeviceNotAvailableException {
+        if (testClassName != null && testClassName.startsWith(".")) {
+            testClassName = pkgName + testClassName;
+        }
+
+        RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(
+                pkgName, RUNNER, getDevice().getIDevice());
+        if (testClassName != null && testMethodName != null) {
+            testRunner.setMethodName(testClassName, testMethodName);
+        } else if (testClassName != null) {
+            testRunner.setClassName(testClassName);
+        }
+
+        for (Map.Entry<String, String> param : params.entrySet()) {
+            testRunner.addInstrumentationArg(param.getKey(), param.getValue());
+        }
+
+        CollectingTestListener listener = new CollectingTestListener();
+        assertTrue(getDevice().runInstrumentationTestsAsUser(testRunner, userId, listener));
+
+        TestRunResult runResult = listener.getCurrentRunResults();
+        if (runResult.getTestResults().size() == 0) {
+            fail("No tests have been executed.");
+            return;
+        }
+
+        printTestResult(runResult);
+        if (runResult.hasFailedTests() || runResult.getNumTestsInState(TestStatus.PASSED) == 0) {
+            fail("Some tests have been failed.");
+        }
+    }
+
+    private void printTestResult(TestRunResult runResult) {
+        for (Map.Entry<TestIdentifier, TestResult> testEntry :
+                runResult.getTestResults().entrySet()) {
+            TestResult testResult = testEntry.getValue();
+
+            final String message = "Test " + testEntry.getKey() + ": " + testResult.getStatus();
+            if (testResult.getStatus() == TestStatus.PASSED) {
+                CLog.i(message);
+            } else {
+                CLog.e(message);
+                CLog.e(testResult.getStackTrace());
+            }
+        }
+    }
+
+    private void removeTestUsers() throws Exception {
+        if (!mIsMultiuserSupported) {
+            return;
+        }
+        getDevice().switchUser(getPrimaryUserId());
+        for (int userId : getDevice().listUsers()) {
+            if (!mOriginalUsers.contains(userId)) {
+                getDevice().removeUser(userId);
+            }
+        }
+    }
+
+    protected int createUser() throws Exception{
+        return getDevice().createUser("TestUser_" + System.currentTimeMillis());
+    }
+
+    protected int createProfile(int parentUserId) throws Exception{
+        final String command = "pm create-user --profileOf " + parentUserId
+                + " --managed TestUser_" + System.currentTimeMillis();
+        CLog.d("Starting command: " + command);
+        final String output = getDevice().executeShellCommand(command);
+        CLog.d("Output for command " + command + ": " + output);
+
+        if (output.startsWith("Success")) {
+            try {
+                return Integer.parseInt(output.substring(output.lastIndexOf(" ")).trim());
+            } catch (NumberFormatException e) {
+                CLog.e("Failed to parse result: %s", output);
+            }
+        } else {
+            CLog.e("Failed to create user: %s", output);
+        }
+        throw new IllegalStateException();
+    }
+
+    /**
+     * Variant of {@link #assertContainsRegex(String,String,String)} using a
+     * generic message.
+     */
+    public MatchResult assertContainsRegex(
+            String expectedRegex, String actual) {
+        return assertContainsRegex(null, expectedRegex, actual);
+    }
+
+    /**
+     * Asserts that {@code expectedRegex} matches any substring of {@code actual}
+     * and fails with {@code message} if it does not.  The Matcher is returned in
+     * case the test needs access to any captured groups.  Note that you can also
+     * use this for a literal string, by wrapping your expected string in
+     * {@link Pattern#quote}.
+     */
+    public MatchResult assertContainsRegex(
+            String message, String expectedRegex, String actual) {
+        if (actual == null) {
+            failNotContains(message, expectedRegex, actual);
+        }
+        Matcher matcher = getMatcher(expectedRegex, actual);
+        if (!matcher.find()) {
+            failNotContains(message, expectedRegex, actual);
+        }
+        return matcher;
+    }
+
+    /**
+     * Asserts that {@code expectedRegex} does not exactly match {@code actual},
+     * and fails with {@code message} if it does. Note that you can also use
+     * this for a literal string, by wrapping your expected string in
+     * {@link Pattern#quote}.
+     */
+    public void assertNotMatchesRegex(
+            String message, String expectedRegex, String actual) {
+        Matcher matcher = getMatcher(expectedRegex, actual);
+        if (matcher.matches()) {
+            failMatch(message, expectedRegex, actual);
+        }
+    }
+
+    private Matcher getMatcher(String expectedRegex, String actual) {
+        Pattern pattern = Pattern.compile(expectedRegex);
+        return pattern.matcher(actual);
+    }
+
+    private void failMatch(
+            String message, String expectedRegex, String actual) {
+        failWithMessage(message, "expected not to match regex:<" + expectedRegex
+                + "> but was:<" + actual + '>');
+    }
+
+    private void failWithMessage(String userMessage, String ourMessage) {
+        fail((userMessage == null)
+                ? ourMessage
+                : userMessage + ' ' + ourMessage);
+    }
+
+    private void failNotContains(
+            String message, String expectedRegex, String actual) {
+        String actualDesc = (actual == null) ? "null" : ('<' + actual + '>');
+        failWithMessage(message, "expected to contain regex:<" + expectedRegex
+                + "> but was:" + actualDesc);
+    }
+}
diff --git a/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/ShortcutManagerBackupTest.java b/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/ShortcutManagerBackupTest.java
new file mode 100644
index 0000000..75e6ef5
--- /dev/null
+++ b/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/ShortcutManagerBackupTest.java
@@ -0,0 +1,201 @@
+/*
+ * 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.shortcuthost;
+
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
+
+public class ShortcutManagerBackupTest extends BaseShortcutManagerHostTest {
+    private static final String LAUNCHER1_APK = "CtsShortcutBackupLauncher1.apk";
+    private static final String LAUNCHER2_APK = "CtsShortcutBackupLauncher2.apk";
+    private static final String LAUNCHER3_APK = "CtsShortcutBackupLauncher3.apk";
+    private static final String PUBLISHER1_APK = "CtsShortcutBackupPublisher1.apk";
+    private static final String PUBLISHER2_APK = "CtsShortcutBackupPublisher2.apk";
+    private static final String PUBLISHER3_APK = "CtsShortcutBackupPublisher3.apk";
+
+    private static final String LAUNCHER1_PKG =
+            "android.content.pm.cts.shortcut.backup.launcher1";
+    private static final String LAUNCHER2_PKG =
+            "android.content.pm.cts.shortcut.backup.launcher2";
+    private static final String LAUNCHER3_PKG =
+            "android.content.pm.cts.shortcut.backup.launcher3";
+    private static final String PUBLISHER1_PKG =
+            "android.content.pm.cts.shortcut.backup.publisher1";
+    private static final String PUBLISHER2_PKG =
+            "android.content.pm.cts.shortcut.backup.publisher2";
+    private static final String PUBLISHER3_PKG =
+            "android.content.pm.cts.shortcut.backup.publisher3";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        clearShortcuts(LAUNCHER1_PKG, getPrimaryUserId());
+        clearShortcuts(LAUNCHER2_PKG, getPrimaryUserId());
+        clearShortcuts(LAUNCHER3_PKG, getPrimaryUserId());
+        clearShortcuts(PUBLISHER1_PKG, getPrimaryUserId());
+        clearShortcuts(PUBLISHER2_PKG, getPrimaryUserId());
+        clearShortcuts(PUBLISHER3_PKG, getPrimaryUserId());
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (DUMPSYS_IN_TEARDOWN) {
+            dumpsys("tearDown");
+        }
+
+        getDevice().uninstallPackage(LAUNCHER1_PKG);
+        getDevice().uninstallPackage(LAUNCHER2_PKG);
+        getDevice().uninstallPackage(LAUNCHER3_PKG);
+
+        getDevice().uninstallPackage(PUBLISHER1_PKG);
+        getDevice().uninstallPackage(PUBLISHER2_PKG);
+        getDevice().uninstallPackage(PUBLISHER3_PKG);
+
+        super.tearDown();
+    }
+
+    private void doBackup() throws DeviceNotAvailableException {
+        CLog.i("Backing up package android...");
+        assertContainsRegex(
+                "^Selected transport android/com.android.internal.backup.LocalTransport",
+                executeShellCommandWithLog(
+                        "bmgr transport android/com.android.internal.backup.LocalTransport"));
+
+        assertContainsRegex(
+                "Wiped",
+                executeShellCommandWithLog(
+                        "bmgr wipe android/com.android.internal.backup.LocalTransport android"));
+
+        assertContainsRegex(
+                "Backup finished with result: Success",
+                executeShellCommandWithLog("bmgr backupnow android"));
+
+    }
+
+    private void doRestore() throws DeviceNotAvailableException {
+        CLog.i("Restoring package android...");
+
+        assertContainsRegex(
+                "\\bdone\\b",
+                executeShellCommandWithLog("bmgr restore 1 android"));
+
+    }
+
+    public void testBackupAndRestore() throws Exception {
+        installAppAsUser(LAUNCHER1_APK, getPrimaryUserId());
+        installAppAsUser(LAUNCHER2_APK, getPrimaryUserId());
+        installAppAsUser(LAUNCHER3_APK, getPrimaryUserId());
+
+        installAppAsUser(PUBLISHER1_APK, getPrimaryUserId());
+        installAppAsUser(PUBLISHER2_APK, getPrimaryUserId());
+        installAppAsUser(PUBLISHER3_APK, getPrimaryUserId());
+
+        // Prepare shortcuts
+        runDeviceTestsAsUser(PUBLISHER1_PKG, ".ShortcutManagerPreBackupTest", getPrimaryUserId());
+        runDeviceTestsAsUser(PUBLISHER2_PKG, ".ShortcutManagerPreBackupTest", getPrimaryUserId());
+        runDeviceTestsAsUser(PUBLISHER3_PKG, ".ShortcutManagerPreBackupTest", getPrimaryUserId());
+
+        runDeviceTestsAsUser(LAUNCHER1_PKG, ".ShortcutManagerPreBackupTest", getPrimaryUserId());
+        runDeviceTestsAsUser(LAUNCHER2_PKG, ".ShortcutManagerPreBackupTest", getPrimaryUserId());
+        runDeviceTestsAsUser(LAUNCHER3_PKG, ".ShortcutManagerPreBackupTest", getPrimaryUserId());
+
+        // Tweak shortcuts a little bit to make disabled shortcuts.
+        runDeviceTestsAsUser(PUBLISHER2_PKG, ".ShortcutManagerPreBackup2Test", getPrimaryUserId());
+
+        // Backup
+        doBackup();
+
+        // Uninstall all apps
+        getDevice().uninstallPackage(LAUNCHER1_PKG);
+        getDevice().uninstallPackage(LAUNCHER2_PKG);
+        getDevice().uninstallPackage(LAUNCHER3_PKG);
+
+        getDevice().uninstallPackage(PUBLISHER1_PKG);
+        getDevice().uninstallPackage(PUBLISHER2_PKG);
+        getDevice().uninstallPackage(PUBLISHER3_PKG);
+
+        // Then restore
+        doRestore();
+
+        // First, restore launcher 1, which shouldn't see any shortcuts from the packages yet.
+        installAppAsUser(LAUNCHER1_APK, getPrimaryUserId());
+        runDeviceTestsAsUser(LAUNCHER1_PKG, ".ShortcutManagerPostBackupTest",
+                "testWithUninstall_beforeAppRestore",
+                getPrimaryUserId());
+
+        // Restore the apps.  Even though launcher 2 hasn't been re-installed yet, they should
+        // still have pinned shortcuts by launcher 2.
+        installAppAsUser(PUBLISHER1_APK, getPrimaryUserId());
+        installAppAsUser(PUBLISHER2_APK, getPrimaryUserId());
+        installAppAsUser(PUBLISHER3_APK, getPrimaryUserId());
+
+        runDeviceTestsAsUser(PUBLISHER1_PKG, ".ShortcutManagerPostBackupTest",
+                "testWithUninstall",
+                getPrimaryUserId());
+
+        runDeviceTestsAsUser(PUBLISHER2_PKG, ".ShortcutManagerPostBackupTest",
+                "testWithUninstall",
+                getPrimaryUserId());
+
+        runDeviceTestsAsUser(PUBLISHER3_PKG, ".ShortcutManagerPostBackupTest",
+                "testWithUninstall",
+                getPrimaryUserId());
+
+        // Now launcher 1 should see shortcuts from these packages.
+        runDeviceTestsAsUser(LAUNCHER1_PKG, ".ShortcutManagerPostBackupTest",
+                "testWithUninstall_afterAppRestore",
+                getPrimaryUserId());
+
+        // Then restore launcher 2 and check.
+        installAppAsUser(LAUNCHER2_APK, getPrimaryUserId());
+        runDeviceTestsAsUser(LAUNCHER2_PKG, ".ShortcutManagerPostBackupTest",
+                "testWithUninstall_afterAppRestore",
+                getPrimaryUserId());
+
+
+        // Run the same package side check.  The result should be the same.
+        runDeviceTestsAsUser(PUBLISHER1_PKG, ".ShortcutManagerPostBackupTest",
+                "testWithUninstall",
+                getPrimaryUserId());
+
+        runDeviceTestsAsUser(PUBLISHER2_PKG, ".ShortcutManagerPostBackupTest",
+                "testWithUninstall",
+                getPrimaryUserId());
+
+        runDeviceTestsAsUser(PUBLISHER3_PKG, ".ShortcutManagerPostBackupTest",
+                "testWithUninstall",
+                getPrimaryUserId());
+    }
+
+    public void testBackupAndRestore_withNoUninstall() throws Exception {
+
+        installAppAsUser(PUBLISHER1_APK, getPrimaryUserId());
+
+        // Prepare shortcuts
+        runDeviceTestsAsUser(PUBLISHER1_PKG, ".ShortcutManagerPreBackupTest", getPrimaryUserId());
+
+        // Backup & restore.
+        doBackup();
+        doRestore();
+
+        // Make sure the manifest shortcuts are re-published.
+        runDeviceTestsAsUser(PUBLISHER1_PKG, ".ShortcutManagerPostBackupTest",
+                "testWithNoUninstall",
+                getPrimaryUserId());
+    }
+}
\ No newline at end of file
diff --git a/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/ShortcutManagerMultiuserTest.java b/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/ShortcutManagerMultiuserTest.java
new file mode 100644
index 0000000..a74ab45
--- /dev/null
+++ b/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/ShortcutManagerMultiuserTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.shortcuthost;
+
+
+public class ShortcutManagerMultiuserTest extends BaseShortcutManagerHostTest {
+    private static final String TARGET_APK = "CtsShortcutMultiuserTest.apk";
+    private static final String TARGET_PKG = "android.content.pm.cts.shortcut.multiuser";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        getDevice().uninstallPackage(TARGET_PKG);
+
+        super.tearDown();
+    }
+
+    public void testManagedUser() throws Exception {
+        if (!mIsMultiuserSupported || !mIsManagedUserSupported) {
+            return;
+        }
+        // First, create users
+        final int profileId = createProfile(getPrimaryUserId());
+
+        installAppAsUser(TARGET_APK, getPrimaryUserId());
+        installAppAsUser(TARGET_APK, profileId);
+
+        runDeviceTestsAsUser(TARGET_PKG, ".ShortcutManagerManagedUserTest",
+                "test01_managedProfileNotStarted", getPrimaryUserId());
+
+        getDevice().startUser(profileId);
+
+        runDeviceTestsAsUser(TARGET_PKG, ".ShortcutManagerManagedUserTest",
+                "test02_createShortuctsOnPrimaryUser", getPrimaryUserId());
+        runDeviceTestsAsUser(TARGET_PKG, ".ShortcutManagerManagedUserTest",
+                "test03_createShortuctsOnManagedProfile", profileId);
+        runDeviceTestsAsUser(TARGET_PKG, ".ShortcutManagerManagedUserTest",
+                "test04_getAndLaunch", getPrimaryUserId());
+    }
+
+    public void testSecondaryUser() throws Exception {
+        if (!mIsMultiuserSupported) {
+            return;
+        }
+        final int secondUserID = createUser();
+
+        getDevice().startUser(secondUserID);
+        getDevice().switchUser(secondUserID);
+        installAppAsUser(TARGET_APK, secondUserID);
+
+        runDeviceTestsAsUser(TARGET_PKG, ".ShortcutManagerSecondaryUserTest", secondUserID);
+
+        getDevice().stopUser(secondUserID);
+    }
+}
diff --git a/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/ShortcutManagerUpgradeTest.java b/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/ShortcutManagerUpgradeTest.java
new file mode 100644
index 0000000..6689d90
--- /dev/null
+++ b/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/ShortcutManagerUpgradeTest.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 android.content.pm.cts.shortcuthost;
+
+
+public class ShortcutManagerUpgradeTest extends BaseShortcutManagerHostTest {
+    private static final String VERSION1_APK = "CtsShortcutUpgradeVersion1.apk";
+    private static final String VERSION2_APK = "CtsShortcutUpgradeVersion2.apk";
+
+    private static final String TARGET_PKG = "android.content.pm.cts.shortcut.upgrade";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        getDevice().uninstallPackage(TARGET_PKG);
+
+        super.tearDown();
+    }
+
+    /**
+     * Make sure that, even when icon resource IDs have changed during an app upgrade,
+     * ShortcutManager correctly resolves the right resources by resource name.
+     */
+    public void testUpgrade() throws Exception {
+        installAppAsUser(VERSION1_APK, getPrimaryUserId());
+
+        runDeviceTestsAsUser(TARGET_PKG, ".ShortcutManagerPreUpgradeTest", getPrimaryUserId());
+
+        installAppAsUser(VERSION2_APK, getPrimaryUserId());
+
+        runDeviceTestsAsUser(TARGET_PKG, ".ShortcutManagerPostUpgradeTest", getPrimaryUserId());
+    }
+}
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 6496a1f..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 2b23d67..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 fbde98a..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 ed7036e..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/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 38443b9..979d18a 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);
         }
     }
 
@@ -497,6 +507,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..afac9a2 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CameraTestUtils.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CameraTestUtils.java
@@ -51,6 +51,7 @@
 import android.util.Log;
 import android.util.Pair;
 import android.util.Size;
+import android.util.Range;
 import android.view.Display;
 import android.view.Surface;
 import android.view.WindowManager;
@@ -1668,6 +1669,22 @@
     }
 
     /**
+     * Get AeAvailableTargetFpsRanges and sort them in descending order by max fps
+     *
+     * @param staticInfo camera static metadata
+     * @return AeAvailableTargetFpsRanges in descending order by max fps
+     */
+    public static Range<Integer>[] getDescendingTargetFpsRanges(StaticMetadata staticInfo) {
+        Range<Integer>[] fpsRanges = staticInfo.getAeAvailableTargetFpsRangesChecked();
+        Arrays.sort(fpsRanges, new Comparator<Range<Integer>>() {
+            public int compare(Range<Integer> r1, Range<Integer> r2) {
+                return r2.getUpper() - r1.getUpper();
+            }
+        });
+        return fpsRanges;
+    }
+
+    /**
      * Calculate output 3A region from the intersection of input 3A region and cropped region.
      *
      * @param requestRegions The input 3A regions
@@ -2179,8 +2196,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/CaptureRequestTest.java b/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
index 5dca2cc..ebda3dd 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
@@ -829,23 +829,23 @@
         CaptureRequest.Builder requestBuilder =
                 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
         int[] availableModes = mStaticInfo.getAvailableNoiseReductionModesChecked();
-        SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
-        startPreview(requestBuilder, maxPrevSize, resultListener);
 
         for (int mode : availableModes) {
             requestBuilder.set(CaptureRequest.NOISE_REDUCTION_MODE, mode);
-            resultListener = new SimpleCaptureCallback();
-            mSession.setRepeatingRequest(requestBuilder.build(), resultListener, mHandler);
-            waitForSettingsApplied(resultListener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
-
-            verifyCaptureResultForKey(CaptureResult.NOISE_REDUCTION_MODE, mode,
-                    resultListener, NUM_FRAMES_VERIFIED);
 
             // Test that OFF and FAST mode should not slow down the frame rate.
             if (mode == CaptureRequest.NOISE_REDUCTION_MODE_OFF ||
                     mode == CaptureRequest.NOISE_REDUCTION_MODE_FAST) {
                 verifyFpsNotSlowDown(requestBuilder, NUM_FRAMES_VERIFIED);
             }
+
+            SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
+            startPreview(requestBuilder, maxPrevSize, resultListener);
+            mSession.setRepeatingRequest(requestBuilder.build(), resultListener, mHandler);
+            waitForSettingsApplied(resultListener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
+
+            verifyCaptureResultForKey(CaptureResult.NOISE_REDUCTION_MODE, mode,
+                    resultListener, NUM_FRAMES_VERIFIED);
         }
 
         stopPreview();
@@ -1086,24 +1086,24 @@
         int[] edgeModes = mStaticInfo.getAvailableEdgeModesChecked();
         CaptureRequest.Builder requestBuilder =
                 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
-        SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
-        startPreview(requestBuilder, maxPrevSize, resultListener);
 
         for (int mode : edgeModes) {
             requestBuilder.set(CaptureRequest.EDGE_MODE, mode);
-            resultListener = new SimpleCaptureCallback();
-            mSession.setRepeatingRequest(requestBuilder.build(), resultListener, mHandler);
-            waitForSettingsApplied(resultListener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
-
-            verifyCaptureResultForKey(CaptureResult.EDGE_MODE, mode, resultListener,
-                    NUM_FRAMES_VERIFIED);
 
             // Test that OFF and FAST mode should not slow down the frame rate.
             if (mode == CaptureRequest.EDGE_MODE_OFF ||
                     mode == CaptureRequest.EDGE_MODE_FAST) {
                 verifyFpsNotSlowDown(requestBuilder, NUM_FRAMES_VERIFIED);
             }
-        }
+
+            SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
+            startPreview(requestBuilder, maxPrevSize, resultListener);
+            mSession.setRepeatingRequest(requestBuilder.build(), resultListener, mHandler);
+            waitForSettingsApplied(resultListener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
+
+            verifyCaptureResultForKey(CaptureResult.EDGE_MODE, mode, resultListener,
+                    NUM_FRAMES_VERIFIED);
+       }
 
         stopPreview();
     }
@@ -2552,7 +2552,7 @@
             frameDurationErrorMargin = 0.015f;
         }
 
-        Range<Integer>[] fpsRanges = mStaticInfo.getAeAvailableTargetFpsRangesChecked();
+        Range<Integer>[] fpsRanges = getDescendingTargetFpsRanges(mStaticInfo);
         boolean antiBandingOffIsSupported = mStaticInfo.isAntiBandingOffModeSupported();
         Range<Integer> fpsRange;
         SimpleCaptureCallback resultListener;
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 b580c87..73845dc 100644
--- a/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java
@@ -451,17 +451,17 @@
      * validated.
      */
     private void previewFpsRangeTestByCamera() throws Exception {
-        Size maxPreviewSz = mOrderedPreviewSizes.get(0);
-        Range<Integer>[] fpsRanges = mStaticInfo.getAeAvailableTargetFpsRangesChecked();
+        Size maxPreviewSz;
+        Range<Integer>[] fpsRanges = getDescendingTargetFpsRanges(mStaticInfo);
         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);
 
             requestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange);
             // Turn off auto antibanding to avoid exposure time and frame duration interference
@@ -477,6 +477,7 @@
                         " mode");
             }
 
+            startPreview(requestBuilder, maxPreviewSz, resultListener);
             resultListener = new SimpleCaptureCallback();
             mSession.setRepeatingRequest(requestBuilder.build(), resultListener, mHandler);
 
@@ -484,9 +485,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 4931563..db70868 100644
--- a/tests/expectations/knownfailures.txt
+++ b/tests/expectations/knownfailures.txt
@@ -158,13 +158,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 e037507..6e08f7d 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;
@@ -287,4 +288,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 1407cc9..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,83 +94,99 @@
     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.get() != nullptr) {
-    *error_msg = "The library \"" + path + "\" should not be accessible";
+    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;
-      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;
+    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;
     }
 
-    // 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;
+    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);
+      } else if (!check_lib(path, library_path, libraries, errors)) {
+        success = false;
       }
     }
   }
 
-  return true;
+  return success;
 }
 
 static bool jobject_array_to_set(JNIEnv* env,
                                  jobjectArray java_libraries_array,
                                  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());
 
+    // 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 = "public library line is malformed: " + soname;
-        return false;
+        *error_msg += "\n---Illegal value at end of line (only 32 or 64 allowed): " + soname;
+        success = false;
+        continue;
       }
 #if defined(__LP64__)
       if (type == "32") {
@@ -214,7 +205,7 @@
     libraries->insert(soname);
   }
 
-  return true;
+  return success;
 }
 
 extern "C" JNIEXPORT jstring JNICALL
@@ -223,21 +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;
-  if (!jobject_array_to_set(env, java_vendor_public_libraries, &vendor_public_libraries, &error)) {
-    return env->NewStringUTF(("Vendor " + error).c_str());
-  }
-  if (!jobject_array_to_set(env, java_system_public_libraries, &system_public_libraries, &error)) {
-    return env->NewStringUTF(("System " + error).c_str());
+  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 1c1ed2b..070ab28 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 1fa634a..13ebeff 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(
@@ -2669,8 +2743,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());
 
@@ -2709,8 +2785,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 188064a..3d0a32b 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/SoundPoolTest.java b/tests/tests/media/src/android/media/cts/SoundPoolTest.java
index 98c2c4d..83f333b 100644
--- a/tests/tests/media/src/android/media/cts/SoundPoolTest.java
+++ b/tests/tests/media/src/android/media/cts/SoundPoolTest.java
@@ -279,8 +279,8 @@
             }
 
             // wait for all sounds to load,
-            // it usually takes about 10 seconds and 25 seconds is used to have some headroom.
-            final long LOAD_TIMEOUT_IN_MS = 25000;
+            // it usually takes about 33 seconds and 50 seconds is used to have some headroom.
+            final long LOAD_TIMEOUT_IN_MS = 50000;
             final long startTime = System.currentTimeMillis();
             synchronized(done) {
                 while (loaded[0] != soundIds.length) {
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 13ff24b..a780317 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
@@ -102,12 +102,6 @@
     /* Key to retrieve resolution string in metrics upon MediaPreparerListener.testEnded() */
     private static final String RESOLUTION_STRING_KEY = "resolution";
 
-    /*
-     * In the case of MediaPreparer error, the default maximum resolution to push to the device.
-     * Pushing higher resolutions may lead to insufficient storage for installing test APKs.
-     * TODO(aaronholden): When the new detection of max resolution is proven stable, throw
-     * a TargetSetupError when detection results in error
-     */
     protected static final Resolution DEFAULT_MAX_RESOLUTION = new Resolution(480, 360);
 
     protected static final Resolution[] RESOLUTIONS = {
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/os/src/android/os/cts/BuildVersionTest.java b/tests/tests/os/src/android/os/cts/BuildVersionTest.java
index 92a4f8a..75c8db5 100644
--- a/tests/tests/os/src/android/os/cts/BuildVersionTest.java
+++ b/tests/tests/os/src/android/os/cts/BuildVersionTest.java
@@ -30,8 +30,8 @@
 
     private static final String LOG_TAG = "BuildVersionTest";
     private static final Set<String> EXPECTED_RELEASES =
-            new HashSet<String>(Arrays.asList("7.0"));
-    private static final int EXPECTED_SDK = 24;
+            new HashSet<String>(Arrays.asList("7.1"));
+    private static final int EXPECTED_SDK = 25;
     private static final String EXPECTED_BUILD_VARIANT = "user";
     private static final String EXPECTED_TAG = "release-keys";
 
diff --git a/tests/tests/permission2/res/raw/android_manifest.xml b/tests/tests/permission2/res/raw/android_manifest.xml
index 2bee7a6..b6b2f92 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/CertificateData.java b/tests/tests/security/src/android/security/cts/CertificateData.java
index f1116e7..c1f59ae 100644
--- a/tests/tests/security/src/android/security/cts/CertificateData.java
+++ b/tests/tests/security/src/android/security/cts/CertificateData.java
@@ -26,6 +26,7 @@
 class CertificateData {
   static final String[] CERTIFICATE_DATA = {
       "91:C6:D6:EE:3E:8A:C8:63:84:E5:48:C2:99:29:5C:75:6C:81:7B:81",
+      "22:FD:D0:B7:FD:A2:4E:0D:AC:49:2C:A0:AC:A6:7B:6A:1F:E3:F7:66",
       "4A:65:D5:F4:1D:EF:39:B8:B8:90:4A:4A:D3:64:81:33:CF:C7:A1:D1",
       "16:32:47:8D:89:F9:21:3A:92:00:85:63:F5:A4:A7:D3:12:40:8A:D6",
       "D1:CB:CA:5D:B2:D5:2A:7F:69:3B:67:4D:E5:F0:5A:1D:0C:95:7D:F0",
@@ -59,6 +60,7 @@
       "A3:F1:33:3F:E2:42:BF:CF:C5:D1:4E:8F:39:42:98:40:68:10:D1:A0",
       "5F:43:E5:B1:BF:F8:78:8C:AC:1C:C7:CA:4A:9A:C6:22:2B:CC:34:C6",
       "2B:8F:1B:57:33:0D:BB:A2:D0:7A:6C:51:F7:0E:E9:0D:DA:B9:AD:8E",
+      "79:5F:88:60:C5:AB:7C:3D:92:E6:CB:F4:8D:E1:45:CD:11:EF:60:0B",
       "A8:98:5D:3A:65:E5:E5:C4:B2:D7:D6:6D:40:C6:DD:2F:B1:9C:54:36",
       "59:22:A1:E1:5A:EA:16:35:21:F8:98:39:6A:46:46:B0:44:1B:0F:A9",
       "D4:DE:20:D0:5E:66:FC:53:FE:1A:50:88:2C:78:DB:28:52:CA:E4:74",
@@ -78,6 +80,7 @@
       "9B:AA:E5:9F:56:EE:21:CB:43:5A:BE:25:93:DF:A7:F0:40:D1:1D:CB",
       "36:79:CA:35:66:87:72:30:4D:30:A5:FB:87:3B:0F:A7:7B:B7:0D:54",
       "1B:8E:EA:57:96:29:1A:C9:39:EA:B8:0A:81:1A:73:73:C0:93:79:67",
+      "6E:26:64:F3:56:BF:34:55:BF:D1:93:3F:7C:01:DE:D8:13:DA:8A:A6",
       "A9:E9:78:08:14:37:58:88:F2:05:19:B0:6D:2B:0D:2B:60:16:90:7D",
       "60:D6:89:74:B5:C2:65:9E:8A:0F:C1:88:7C:88:D2:46:69:1B:18:2C",
       "D8:EB:6B:41:51:92:59:E0:F3:E7:85:00:C0:3D:B6:88:97:C9:EE:FC",
@@ -86,6 +89,7 @@
       "22:D5:D8:DF:8F:02:31:D1:8D:F7:9D:B7:CF:8A:2D:64:C9:3F:6C:3A",
       "F3:73:B3:87:06:5A:28:84:8A:F2:F3:4A:CE:19:2B:DD:C7:8E:9C:AC",
       "06:08:3F:59:3F:15:A1:04:A0:69:A4:6B:A9:03:D0:06:B7:97:09:91",
+      "CA:BD:2A:79:A1:07:6A:31:F2:1D:25:36:35:CB:03:9D:43:29:A5:E8",
       "43:13:BB:96:F1:D5:86:9B:C1:4E:6A:92:F6:CF:F6:34:69:87:82:37",
       "8A:5C:8C:EE:A5:03:E6:05:56:BA:D8:1B:D4:F6:C9:B0:ED:E5:2F:E0",
       "F1:8B:53:8D:1B:E9:03:B6:A6:F0:56:43:5B:17:15:89:CA:F3:6B:F2",
@@ -111,12 +115,14 @@
       "C9:A8:B9:E7:55:80:5E:58:E3:53:77:A7:25:EB:AF:C3:7B:27:CC:D7",
       "E2:B8:29:4B:55:84:AB:6B:58:C2:90:46:6C:AC:3F:B8:39:8F:84:83",
       "1F:49:14:F7:D8:74:95:1D:DD:AE:02:C0:BE:FD:3A:2D:82:75:51:85",
+      "9F:F1:71:8D:92:D5:9A:F3:7D:74:97:B4:BC:6F:84:68:0B:BA:B6:66",
       "B5:61:EB:EA:A4:DE:E4:25:4B:69:1A:98:A5:57:47:C2:34:C7:D9:71",
       "07:E0:32:E0:20:B7:2C:3F:19:2F:06:28:A2:59:3A:19:A7:0F:06:9E",
       "B9:42:94:BF:91:EA:8F:B6:4B:E6:10:97:C7:FB:00:13:59:B6:76:CB",
       "D6:DA:A8:20:8D:09:D2:15:4D:24:B5:2F:CB:34:6E:B2:58:B2:8A:58",
       "32:3C:11:8E:1B:F7:B8:B6:52:54:E2:E2:10:0D:D6:02:90:37:F0:96",
       "E7:A1:90:29:D3:D5:52:DC:0D:0F:C6:92:D3:EA:88:0D:15:2E:1A:6B",
+      "79:91:E8:34:F7:E2:EE:DD:08:95:01:52:E9:55:2D:14:E9:58:D5:7E",
       "67:65:0D:F1:7E:8E:7E:5B:82:40:A4:F4:56:4B:CF:E2:3D:69:C6:F0",
       "FE:B8:C4:32:DC:F9:76:9A:CE:AE:3D:D8:90:8F:FD:28:86:65:64:7D",
       "4A:BD:EE:EC:95:0D:35:9C:89:AE:C7:52:A1:2C:5B:29:F6:D6:AA:0C",
@@ -154,7 +160,9 @@
       "F1:7F:6F:B6:31:DC:99:E3:A3:C8:7F:FE:1C:F1:81:10:88:D9:60:33",
       "9D:70:BB:01:A5:A4:A0:18:11:2E:F7:1C:01:B9:32:C5:34:E7:88:A8",
       "96:C9:1B:0B:95:B4:10:98:42:FA:D0:D8:22:79:FE:60:FA:B9:16:83",
+      "4F:65:8E:1F:E9:06:D8:28:02:E9:54:47:41:C9:54:25:5D:69:CC:1A",
       "D8:A6:33:2C:E0:03:6F:B1:85:F6:63:4F:7D:6A:06:65:26:32:28:27",
+      "01:0C:06:95:A6:98:19:14:FF:BF:5F:C6:B0:B6:95:EA:29:E9:12:A6",
       "0F:F9:40:76:18:D3:D7:6A:4B:98:F0:A8:35:9E:0C:FD:27:AC:CC:ED",
       "CC:AB:0E:A0:4C:23:01:D6:69:7B:DD:37:9F:CD:12:EB:24:E3:94:9D",
       "48:12:BD:92:3C:A8:C4:39:06:E7:30:6D:27:96:E6:A4:CF:22:2E:7D",
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..8c03be7
--- /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-minus-junit4 \
+    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..1fc96fb
--- /dev/null
+++ b/tests/tests/shortcutmanager/AndroidManifest.xml
@@ -0,0 +1,59 @@
+<?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>
+
+        <!-- It's not exporeted, but should still be launchable. -->
+        <activity android:name="android.content.pm.cts.shortcutmanager.ShortcutLaunchedActivity"
+            android:exported="false"/>
+    </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..7ca5336
--- /dev/null
+++ b/tests/tests/shortcutmanager/common/src/android.content.pm.cts.shortcutmanager.common/Constants.java
@@ -0,0 +1,40 @@
+/*
+ * 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";
+
+    public static final String TEST_INLINE_REPLY_SHOW = "testInlineReplyShow";
+    public static final String TEST_INLINE_REPLY_CHECK = "testInlineReplyCheck";
+
+    public static final String INLINE_REPLY_TITLE = "InlineReplyTestTitle";
+    public static final String INLINE_REPLY_REMOTE_INPUT_CAPTION = "__INLINE_REPLY_REMOTE_INPUT__";
+}
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..ee92b8c
--- /dev/null
+++ b/tests/tests/shortcutmanager/packages/packagemanifest_nonshared/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?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" android:enabled="true" android:exported="false">
+        </activity>
+    </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/ShortcutLaunchedActivity.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutLaunchedActivity.java
new file mode 100644
index 0000000..61f94d4
--- /dev/null
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutLaunchedActivity.java
@@ -0,0 +1,102 @@
+/*
+ * 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;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Activity that's started from shortcuts during CTS.  This one closes itself when it's shown,
+ * and record all the incoming intents, which we'll examine during the tests.
+ */
+public class ShortcutLaunchedActivity extends Activity {
+    private static final String TAG = "ShortcutLA";
+
+    private static final AtomicInteger sNextInstanceId = new AtomicInteger();
+
+    private final int mInstanceId = sNextInstanceId.getAndIncrement();
+
+    // @GuardedBy("sReceivedIntents")
+    private static final ArrayList<Intent> sReceivedIntents = new ArrayList<>();
+
+    private Handler mHandler = new Handler();
+
+    private Intent mIntentToAdd;
+
+    private void log(String action) {
+        Log.i(TAG, String.format("Activity #%d %s: intent=%s",
+                mInstanceId, action, getIntent()));
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mIntentToAdd = getIntent();
+
+        log("onCreate");
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        log("onResume");
+
+        synchronized (sReceivedIntents) {
+            // Make sure we only add it once, ever.
+            if (mIntentToAdd != null) {
+                sReceivedIntents.add(new Intent(getIntent()));
+                mIntentToAdd = null;
+            }
+        }
+        mHandler.post(() -> {
+            onBackPressed();
+        });
+    }
+
+    @Override
+    public void onBackPressed() {
+        log("onBackPressed");
+        super.onBackPressed();
+    }
+
+    @Override
+    protected void onDestroy() {
+        log("onDestroy");
+
+        super.onDestroy();
+    }
+
+    public static void clearIntents() {
+        synchronized (sReceivedIntents) {
+            sReceivedIntents.clear();
+        }
+    }
+
+    public static List<Intent> getIntents() {
+        synchronized (sReceivedIntents) {
+            return new ArrayList(sReceivedIntents);
+        }
+    }
+}
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..4a086a8
--- /dev/null
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerClientApiTest.java
@@ -0,0 +1,1812 @@
+/*
+ * 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.parceled;
+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.
+ */
+@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();
+        });
+
+        // Re-publish will implicitly re-enable.
+        runWithCaller(mPackageContext2, () -> {
+            getManager().addDynamicShortcuts(list(makeShortcut("s2", "re-published")));
+        });
+
+        runWithCaller(mPackageContext2, () -> {
+            assertWith(getManager().getDynamicShortcuts())
+                    .areAllEnabled()
+                    .areAllDynamic()
+                    .haveIds("s3", "s2")
+                    .forShortcutWithId("s3", si -> {
+                        assertEquals("3b", si.getShortLabel());
+                    })
+                    .forShortcutWithId("s2", si -> {
+                        assertEquals("re-published", si.getShortLabel());
+                    })
+            ;
+            assertWith(getManager().getPinnedShortcuts())
+                    .areAllPinned()
+                    .haveIds("s1", "s2")
+            ;
+            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());
+                    })
+                    ;
+        });
+    }
+
+    public void testMiscShortcutInfo() {
+        final Icon icon1 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+                getTestContext().getResources(), R.drawable.black_16x64));
+        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();
+
+        final ShortcutInfo clone = parceled(source);
+
+        // Check each field.
+        assertWith(list(clone))
+                .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());
+
+                    assertEquals(getUserHandle(), si.getUserHandle());
+                });
+    }
+
+    // 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..0242b41
--- /dev/null
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerCtsTestsBase.java
@@ -0,0 +1,528 @@
+/*
+ * 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 {
+    protected static final String TAG = "ShortcutCTS";
+
+    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) {
+        return getShortcutsAsLauncher(flags, packageName, null, 0, null);
+    }
+
+    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..177ce5e
--- /dev/null
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerLauncherApiTest.java
@@ -0,0 +1,345 @@
+/*
+ * 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() == 3,
+                    "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() == 3,
+                    "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() == 2,
+                    "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));
+    }
+}
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerLauncherCallbackTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerLauncherCallbackTest.java
new file mode 100644
index 0000000..758b0fd
--- /dev/null
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerLauncherCallbackTest.java
@@ -0,0 +1,323 @@
+/*
+ * 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.list;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.retryUntil;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.setDefaultLauncher;
+
+import android.content.Context;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.ShortcutListAsserter;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Predicate;
+
+@SmallTest
+public class ShortcutManagerLauncherCallbackTest extends ShortcutManagerCtsTestsBase {
+
+    private static class MyCallback extends LauncherApps.Callback {
+        private boolean called;
+        private String lastPackage;
+        private final List<ShortcutInfo> lastShortcuts = new ArrayList<>();
+
+        @Override
+        public void onPackageRemoved(String packageName, UserHandle user) {
+        }
+
+        @Override
+        public void onPackageAdded(String packageName, UserHandle user) {
+        }
+
+        @Override
+        public void onPackageChanged(String packageName, UserHandle user) {
+        }
+
+        @Override
+        public void onPackagesAvailable(String[] packageNames, UserHandle user, boolean replacing) {
+        }
+
+        @Override
+        public void onPackagesUnavailable(String[] packageNames, UserHandle user,
+                boolean replacing) {
+        }
+
+        @Override
+        public synchronized void onShortcutsChanged(
+                String packageName, List<ShortcutInfo> shortcuts, UserHandle user) {
+            final StringBuilder sb = new StringBuilder();
+            for (ShortcutInfo si : shortcuts) {
+                if (sb.length() > 0) {
+                    sb.append(", ");
+                }
+                sb.append(si.getId());
+            }
+
+            Log.i(TAG, "package=" + packageName + " shortcuts=" + sb.toString());
+            lastPackage = packageName;
+            lastShortcuts.clear();
+            lastShortcuts.addAll(shortcuts);
+            called = true;
+        }
+
+        public synchronized void reset() {
+            lastPackage = null;
+            lastShortcuts.clear();
+            called = false;
+        }
+
+        public synchronized boolean isCalled() {
+            return called;
+        }
+
+        public synchronized ShortcutListAsserter assertCalled(Context clientContext) {
+            assertEquals(clientContext.getPackageName(), lastPackage);
+            return assertWith(lastShortcuts);
+        }
+
+        public synchronized List<ShortcutInfo> getList() {
+            return lastShortcuts;
+        }
+
+        public synchronized boolean isShortcutById(String id, Predicate<ShortcutInfo> predicate) {
+            for (ShortcutInfo si : lastShortcuts) {
+                if (id.equals(si.getId()) && predicate.test(si)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    public void testRegisterAndUnRegister() {
+        final MyCallback c = new MyCallback();
+        final Handler handler = new Handler(Looper.getMainLooper());
+        getLauncherApps().registerCallback(c, handler);
+        getLauncherApps().unregisterCallback(c);
+    }
+
+    public void testCallbacks() {
+        final MyCallback c = new MyCallback();
+
+        setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+        final Handler handler = new Handler(Looper.getMainLooper());
+
+        final AtomicBoolean registered = new AtomicBoolean(false);
+
+        final Runnable reset = () -> {
+            runWithCaller(mLauncherContext1, () -> {
+                if (registered.get()) {
+                    getLauncherApps().unregisterCallback(c);
+                    registered.set(false);
+                }
+                c.reset();
+                getLauncherApps().registerCallback(c, handler);
+                registered.set(true);
+            });
+        };
+        reset.run();
+        try {
+            //-----------------------
+            runWithCaller(mPackageContext1, () -> {
+                assertTrue(getManager().setDynamicShortcuts(list(
+                        makeShortcut("s1"),
+                        makeShortcut("s2")
+                )));
+            });
+            retryUntil(() -> c.isCalled(), "callback not called.");
+            c.assertCalled(mPackageContext1)
+                    .haveIds("s1", "s2")
+                    .areAllEnabled();
+            reset.run();
+
+            //-----------------------
+            runWithCaller(mPackageContext1, () -> {
+                assertTrue(getManager().addDynamicShortcuts(list(
+                        makeShortcutWithRank("sx", 1)
+                )));
+            });
+            retryUntil(() -> c.isCalled(), "callback not called.");
+            c.assertCalled(mPackageContext1)
+                    .haveIds("s1", "s2", "sx")
+                    .areAllEnabled();
+            reset.run();
+
+            //-----------------------
+            runWithCaller(mPackageContext1, () -> {
+                assertTrue(getManager().updateShortcuts(list(
+                        makeShortcut("s2")
+                )));
+            });
+            retryUntil(() -> c.isCalled(), "callback not called.");
+            c.assertCalled(mPackageContext1)
+                    .haveIds("s1", "s2", "sx")
+                    .areAllEnabled();
+            reset.run();
+
+            //-----------------------
+            runWithCaller(mPackageContext1, () -> {
+                assertTrue(getManager().updateShortcuts(list(
+                        makeShortcut("sx")
+                )));
+            });
+            retryUntil(() -> c.isCalled(), "callback not called.");
+            c.assertCalled(mPackageContext1)
+                    .haveIds("s1", "s2", "sx")
+                    .areAllEnabled();
+            reset.run();
+
+            //-----------------------
+            runWithCaller(mPackageContext1, () -> {
+                enableManifestActivity("Launcher_manifest_1", true);
+                retryUntil(() -> getManager().getManifestShortcuts().size() == 1,
+                        "Manifest shortcuts didn't show up");
+            });
+            retryUntil(() -> c.isCalled(), "callback not called.");
+            c.assertCalled(mPackageContext1)
+                    .haveIds("s1", "s2", "sx", "ms1")
+                    .areAllEnabled();
+            reset.run();
+
+            //-----------------------
+            runWithCaller(mPackageContext1, () -> {
+                enableManifestActivity("Launcher_manifest_2", true);
+                retryUntil(() -> getManager().getManifestShortcuts().size() == 3,
+                        "Manifest shortcuts didn't show up");
+            });
+            retryUntil(() -> c.isCalled(), "callback not called.");
+            c.assertCalled(mPackageContext1)
+                    .haveIds("s1", "s2", "sx", "ms1", "ms21", "ms22")
+                    .areAllEnabled();
+            reset.run();
+
+            //-----------------------
+            // Pin some shortcuts.
+            runWithCaller(mLauncherContext1, () -> {
+                getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
+                        list("s1", "ms1", "ms21"), getUserHandle());
+            });
+
+            setDefaultLauncher(getInstrumentation(), mLauncherContext2);
+            runWithCaller(mLauncherContext2, () -> {
+                getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
+                        list("s1", "s2", "s3", "ms22"), getUserHandle());
+            });
+
+            setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+            reset.run();
+
+            //-----------------------
+            runWithCaller(mPackageContext1, () -> {
+                getManager().removeDynamicShortcuts(list("s1", "s2"));
+            });
+            retryUntil(() -> c.isCalled(), "callback not called.");
+
+            // s2 is still pinned by L2, but not visible to L1.
+            c.assertCalled(mPackageContext1)
+                    .haveIds("s1", "sx", "ms1", "ms21", "ms22")
+                    .areAllEnabled();
+            reset.run();
+
+            //-----------------------
+            runWithCaller(mPackageContext1, () -> {
+                enableManifestActivity("Launcher_manifest_2", false);
+
+                retryUntil(() -> getManager().getManifestShortcuts().size() == 1,
+                        "Manifest shortcuts didn't show up");
+            });
+            retryUntil(() -> c.isCalled(), "callback not called.");
+            c.assertCalled(mPackageContext1)
+                    .haveIds("s1", "sx", "ms1", "ms21")
+
+                    .selectByIds("s1", "sx", "ms1")
+                    .areAllEnabled()
+
+                    .revertToOriginalList()
+                    .selectByIds("ms21")
+                    .areAllDisabled();
+            reset.run();
+
+            //-----------------------
+            runWithCaller(mPackageContext1, () -> {
+                getManager().disableShortcuts(list("s1"));
+            });
+            retryUntil(() -> (c.isCalled() && c.isShortcutById("s1", si -> !si.isEnabled())),
+                    "s1 not disabled");
+
+            c.assertCalled(mPackageContext1)
+                    .haveIds("s1", "sx", "ms1", "ms21")
+
+                    .selectByIds("sx", "ms1")
+                    .areAllEnabled()
+
+                    .revertToOriginalList()
+                    .selectByIds("s1", "ms21")
+                    .areAllDisabled();
+            reset.run();
+
+            //-----------------------
+            runWithCaller(mPackageContext1, () -> {
+                getManager().enableShortcuts(list("s1"));
+            });
+            retryUntil(() -> c.isCalled(), "callback not called.");
+            c.assertCalled(mPackageContext1)
+                    .haveIds("s1", "sx", "ms1", "ms21")
+
+                    .selectByIds("s1", "sx", "ms1")
+                    .areAllEnabled()
+
+                    .revertToOriginalList()
+                    .selectByIds("ms21")
+                    .areAllDisabled();
+            reset.run();
+
+            //-----------------------
+            runWithCaller(mPackageContext1, () -> {
+                getManager().enableShortcuts(list("s2"));
+            });
+            retryUntil(() -> c.isCalled(), "callback not called.");
+            c.assertCalled(mPackageContext1)
+                    .haveIds("s1", "sx", "ms1", "ms21")
+
+                    .selectByIds("s1", "sx", "ms1")
+                    .areAllEnabled()
+
+                    .revertToOriginalList()
+                    .selectByIds("ms21")
+                    .areAllDisabled();
+            reset.run();
+
+        } finally {
+            runWithCaller(mLauncherContext1, () -> {
+                if (registered.get()) {
+                    getLauncherApps().unregisterCallback(c);
+                    registered.set(false);
+                }
+            });
+        }
+    }
+}
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/ShortcutManagerMultiLauncherTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMultiLauncherTest.java
new file mode 100644
index 0000000..78dbccd
--- /dev/null
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMultiLauncherTest.java
@@ -0,0 +1,271 @@
+/*
+ * 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.test.suitebuilder.annotation.SmallTest;
+
+@SmallTest
+public class ShortcutManagerMultiLauncherTest extends ShortcutManagerCtsTestsBase {
+    /**
+     * Make sure diffrerent launchers will have different set of pinned shortcuts.
+     */
+    public void testPinShortcuts() {
+        runWithCaller(mPackageContext1, () -> {
+            enableManifestActivity("Launcher_manifest_1", true);
+            enableManifestActivity("Launcher_manifest_2", true);
+
+            retryUntil(() -> getManager().getManifestShortcuts().size() == 3,
+                    "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();
+            assertWith(getManager().getManifestShortcuts())
+                    .haveIds("ms1", "ms21", "ms22")
+                    .areAllManifest()
+                    .areAllEnabled();
+        });
+        runWithCaller(mPackageContext2, () -> {
+            enableManifestActivity("Launcher_manifest_1", true);
+            enableManifestActivity("Launcher_manifest_3", true);
+
+            retryUntil(() -> getManager().getManifestShortcuts().size() == 3,
+                    "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();
+            assertWith(getManager().getManifestShortcuts())
+                    .haveIds("ms1", "ms31", "ms32")
+                    .areAllManifest()
+                    .areAllEnabled();
+        });
+        setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+        runWithCaller(mLauncherContext1, () -> {
+            getLauncherApps().pinShortcuts(
+                    mPackageContext1.getPackageName(),
+                    list("s1", "s2", "s3", "ms1", "ms21"), getUserHandle());
+            getLauncherApps().pinShortcuts(
+                    mPackageContext2.getPackageName(),
+                    list("s2", "s3", "ms31"), getUserHandle());
+        });
+
+        setDefaultLauncher(getInstrumentation(), mLauncherContext2);
+
+        runWithCaller(mLauncherContext2, () -> {
+            getLauncherApps().pinShortcuts(
+                    mPackageContext1.getPackageName(),
+                    list("s3", "s4", "ms22"), getUserHandle());
+            getLauncherApps().pinShortcuts(
+                    mPackageContext2.getPackageName(),
+                    list("s1", "s2", "s3", "ms32"), getUserHandle());
+        });
+
+        runWithCaller(mPackageContext1, () -> {
+            assertWith(getManager().getDynamicShortcuts())
+                    .haveIds("s1", "s2", "s3", "s4", "s5")
+                    .areAllDynamic()
+                    .areAllEnabled();
+            assertWith(getManager().getManifestShortcuts())
+                    .haveIds("ms1", "ms21", "ms22")
+                    .areAllManifest()
+                    .areAllEnabled();
+            assertWith(getManager().getPinnedShortcuts())
+                    .haveIds("s1", "s2", "s3", "s4", "ms1", "ms21", "ms22")
+                    .areAllEnabled();
+
+            // Then remove some
+            getManager().removeDynamicShortcuts(list("s1", "s2", "s5"));
+            getManager().disableShortcuts(list("s4"));
+
+            enableManifestActivity("Launcher_manifest_1", false);
+            retryUntil(() -> getManager().getManifestShortcuts().size() == 2,
+                    "Manifest shortcuts didn't show up");
+
+            assertWith(getManager().getDynamicShortcuts())
+                    .haveIds("s3")
+                    .areAllDynamic()
+                    .areAllEnabled();
+            assertWith(getManager().getManifestShortcuts())
+                    .haveIds("ms21", "ms22")
+                    .areAllManifest()
+                    .areAllEnabled();
+            assertWith(getManager().getPinnedShortcuts())
+                    .haveIds("s1", "s2", "s3", "s4", "ms1", "ms21", "ms22")
+                    ;
+
+        });
+        runWithCaller(mPackageContext2, () -> {
+            assertWith(getManager().getDynamicShortcuts())
+                    .haveIds("s1", "s2", "s3", "s4", "s5")
+                    .areAllDynamic()
+                    .areAllEnabled();
+            assertWith(getManager().getManifestShortcuts())
+                    .haveIds("ms1", "ms31", "ms32")
+                    .areAllManifest()
+                    .areAllEnabled();
+            assertWith(getManager().getPinnedShortcuts())
+                    .haveIds("s1", "s2", "s3", "ms31", "ms32")
+                    .areAllEnabled();
+
+            // Then remove some
+            getManager().removeDynamicShortcuts(list("s1", "s2", "s3", "s4"));
+
+            enableManifestActivity("Launcher_manifest_3", false);
+            retryUntil(() -> getManager().getManifestShortcuts().size() == 1,
+                    "Manifest shortcuts didn't show up");
+
+            assertWith(getManager().getDynamicShortcuts())
+                    .haveIds("s5")
+                    .areAllDynamic()
+                    .areAllEnabled();
+            assertWith(getManager().getManifestShortcuts())
+                    .haveIds("ms1")
+                    .areAllManifest()
+                    .areAllEnabled();
+            assertWith(getManager().getPinnedShortcuts())
+                    .haveIds("s1", "s2", "s3", "ms31", "ms32")
+                    ;
+        });
+
+        setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+        runWithCaller(mLauncherContext1, () -> {
+            // For package 1.
+            assertWith(getShortcutsAsLauncher(
+                    FLAG_MATCH_DYNAMIC | FLAG_GET_KEY_FIELDS_ONLY,
+                    mPackageContext1.getPackageName()))
+                    .haveIds("s3")
+                    .areAllEnabled();
+            assertWith(getShortcutsAsLauncher(
+                    FLAG_MATCH_MANIFEST | FLAG_GET_KEY_FIELDS_ONLY,
+                    mPackageContext1.getPackageName()))
+                    .haveIds("ms21", "ms22")
+                    .areAllEnabled();
+            assertWith(getShortcutsAsLauncher(
+                    FLAG_MATCH_PINNED | FLAG_GET_KEY_FIELDS_ONLY,
+                    mPackageContext1.getPackageName()))
+                    .haveIds("s1", "s2", "s3", "ms1", "ms21")
+
+                    .selectByIds("s1", "s2", "s3", "ms21")
+                    .areAllEnabled()
+
+                    .revertToOriginalList()
+                    .selectByIds("ms1")
+                    .areAllDisabled();
+
+            // For package 2.
+            assertWith(getShortcutsAsLauncher(
+                    FLAG_MATCH_DYNAMIC | FLAG_GET_KEY_FIELDS_ONLY,
+                    mPackageContext2.getPackageName()))
+                    .haveIds("s5")
+                    .areAllEnabled();
+            assertWith(getShortcutsAsLauncher(
+                    FLAG_MATCH_MANIFEST | FLAG_GET_KEY_FIELDS_ONLY,
+                    mPackageContext2.getPackageName()))
+                    .haveIds("ms1")
+                    .areAllEnabled();
+            assertWith(getShortcutsAsLauncher(
+                    FLAG_MATCH_PINNED | FLAG_GET_KEY_FIELDS_ONLY,
+                    mPackageContext2.getPackageName()))
+                    .haveIds("s2", "s3", "ms31")
+
+                    .selectByIds("s2", "s3")
+                    .areAllEnabled()
+
+                    .revertToOriginalList()
+                    .selectByIds("ms31")
+                    .areAllDisabled();
+        });
+
+        setDefaultLauncher(getInstrumentation(), mLauncherContext2);
+
+        runWithCaller(mLauncherContext2, () -> {
+            // For package 1.
+            assertWith(getShortcutsAsLauncher(
+                    FLAG_MATCH_DYNAMIC | FLAG_GET_KEY_FIELDS_ONLY,
+                    mPackageContext1.getPackageName()))
+                    .haveIds("s3")
+                    .areAllEnabled();
+            assertWith(getShortcutsAsLauncher(
+                    FLAG_MATCH_MANIFEST | FLAG_GET_KEY_FIELDS_ONLY,
+                    mPackageContext1.getPackageName()))
+                    .haveIds("ms21", "ms22")
+                    .areAllEnabled();
+            assertWith(getShortcutsAsLauncher(
+                    FLAG_MATCH_PINNED | FLAG_GET_KEY_FIELDS_ONLY,
+                    mPackageContext1.getPackageName()))
+                    .haveIds("s3", "s4", "ms22")
+
+                    .selectByIds("s3", "ms22")
+                    .areAllEnabled()
+
+                    .revertToOriginalList()
+                    .selectByIds("s4")
+                    .areAllDisabled();
+
+            // For package 2.
+            assertWith(getShortcutsAsLauncher(
+                    FLAG_MATCH_DYNAMIC | FLAG_GET_KEY_FIELDS_ONLY,
+                    mPackageContext2.getPackageName()))
+                    .haveIds("s5")
+                    .areAllEnabled();
+            assertWith(getShortcutsAsLauncher(
+                    FLAG_MATCH_MANIFEST | FLAG_GET_KEY_FIELDS_ONLY,
+                    mPackageContext2.getPackageName()))
+                    .haveIds("ms1")
+                    .areAllEnabled();
+            assertWith(getShortcutsAsLauncher(
+                    FLAG_MATCH_PINNED | FLAG_GET_KEY_FIELDS_ONLY,
+                    mPackageContext2.getPackageName()))
+                    .haveIds("s1", "s2", "s3", "ms32")
+
+                    .selectByIds("s1", "s2", "s3")
+                    .areAllEnabled()
+
+                    .revertToOriginalList()
+                    .selectByIds("ms32")
+                    .areAllDisabled();
+        });
+    }
+}
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..e80b66e
--- /dev/null
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerStartShortcutTest.java
@@ -0,0 +1,340 @@
+/*
+ * 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 com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.retryUntil;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.setDefaultLauncher;
+
+import android.app.ActivityOptions;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.internal.runners.statements.ExpectException;
+
+import java.util.List;
+
+@SmallTest
+public class ShortcutManagerStartShortcutTest extends ShortcutManagerCtsTestsBase {
+    private ComponentName mLaunchedActivity;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mLaunchedActivity = new ComponentName(getTestContext(), ShortcutLaunchedActivity.class);
+    }
+
+    private List<Intent> launchShortcutAndGetIntents(Context launcher, Context client,
+            String id, int expectedNumIntents) {
+        return launchShortcutAndGetIntents(launcher, client, id, expectedNumIntents, null, null);
+    }
+
+    private List<Intent> launchShortcutAndGetIntents(Context launcher, Context client,
+            String id, int expectedNumIntents, Rect rect, Bundle options) {
+
+        ShortcutLaunchedActivity.clearIntents();
+
+        runWithCaller(launcher, () -> {
+            getLauncherApps().startShortcut(client.getPackageName(), id, rect, options,
+                    getUserHandle());
+        });
+
+        retryUntil(() -> ShortcutLaunchedActivity.getIntents().size() == expectedNumIntents,
+                "No activities launched");
+
+        return ShortcutLaunchedActivity.getIntents();
+    }
+
+    private void assertShortcutStarts(Context launcher, Context client, String id) {
+        final List<Intent> launched = launchShortcutAndGetIntents(launcher, client, id, 1);
+        assertTrue(launched.size() > 0);
+    }
+
+    private void assertShortcutCantStart(Context launcher, Context client, String id,
+            Class<? extends Throwable> exceptionClass) {
+        runWithCaller(launcher, () -> {
+            assertExpectException(exceptionClass, "", () -> {
+
+                getLauncherApps().startShortcut(client.getPackageName(), id, null, null,
+                        getUserHandle());
+            });
+        });
+    }
+
+    /**
+     * Start a single activity.
+     */
+    public void testStartSingle() {
+        setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+        Intent i = new Intent(Intent.ACTION_MAIN)
+                .setComponent(mLaunchedActivity)
+                .setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
+                .putExtra("k1", "v1");
+
+        runWithCaller(mPackageContext1, () -> {
+            assertTrue(getManager().addDynamicShortcuts(list(
+                    makeShortcutBuilder("s1").setShortLabel("abc")
+                            .setIntent(i).build()
+                    )));
+        });
+
+        List<Intent> launched = launchShortcutAndGetIntents(mLauncherContext1, mPackageContext1,
+                "s1", 1);
+        assertEquals(1, launched.size());
+        assertEquals(Intent.ACTION_MAIN, launched.get(0).getAction());
+        assertTrue((launched.get(0).getFlags() & Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0);
+        assertEquals("v1", launched.get(0).getStringExtra("k1"));
+    }
+
+    /**
+     * Start multiple activities.
+     */
+    public void testStartMultiple() {
+        setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+        Intent i1 = new Intent("a1")
+                .setComponent(mLaunchedActivity)
+                .setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
+                .putExtra("k1", "v1");
+        Intent i2 = new Intent("a2")
+                .setComponent(mLaunchedActivity)
+                .setFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+        Intent i3 = new Intent("a3")
+                .setComponent(mLaunchedActivity)
+                .putExtra("kx", "vx");
+
+        runWithCaller(mPackageContext1, () -> {
+            assertTrue(getManager().addDynamicShortcuts(list(
+                    makeShortcutBuilder("s1").setShortLabel("abc")
+                            .setIntents(new Intent[]{i1, i2, i3}).build()
+                    )));
+        });
+
+        List<Intent> launched = launchShortcutAndGetIntents(mLauncherContext1, mPackageContext1,
+                "s1", 3);
+        assertEquals(3, launched.size());
+
+        Intent i = launched.get(2);
+        assertEquals("a1", i.getAction());
+        assertTrue((i.getFlags() & Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0);
+        assertTrue((i.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0);
+        assertEquals("v1", i.getStringExtra("k1"));
+
+        i = launched.get(1);
+        assertEquals("a2", i.getAction());
+        assertTrue((i.getFlags() & Intent.FLAG_ACTIVITY_NO_ANIMATION) == 0);
+        assertTrue((i.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0);
+        assertEquals(null, i.getExtras());
+
+        i = launched.get(0);
+        assertEquals("a3", i.getAction());
+        assertTrue((i.getFlags() & Intent.FLAG_ACTIVITY_NO_ANIMATION) == 0);
+        assertTrue((i.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0);
+        assertEquals("vx", i.getStringExtra("kx"));
+    }
+
+    /**
+     * Non default launcher can't start.
+     */
+    public void testNonDefaultLauncherCantStart() {
+
+        testStartSingle();
+
+        setDefaultLauncher(getInstrumentation(), mLauncherContext2);
+
+        // L2 can start it.
+        assertShortcutStarts(mLauncherContext2, mPackageContext1, "s1");
+
+        // L1 no longer can start it.
+        assertShortcutCantStart(mLauncherContext1, mPackageContext1, "s1",
+                SecurityException.class);
+    }
+
+    public void testShortcutNoLongerExists() {
+
+        // Let it publish a shortcut.
+        testStartMultiple();
+
+        // then remove it.
+        runWithCaller(mPackageContext1, () -> {
+            getManager().removeAllDynamicShortcuts();
+        });
+
+        setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+        assertShortcutCantStart(mLauncherContext1, mPackageContext1, "s1",
+                ActivityNotFoundException.class);
+    }
+
+    public void testShortcutWrongId() {
+        Intent i = new Intent(Intent.ACTION_MAIN)
+                .setComponent(mLaunchedActivity)
+                .setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
+                .putExtra("k1", "v1");
+
+        runWithCaller(mPackageContext1, () -> {
+            assertTrue(getManager().addDynamicShortcuts(list(
+                    makeShortcutBuilder("s1").setShortLabel("abc")
+                            .setIntent(i).build()
+            )));
+        });
+
+        setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+        assertShortcutCantStart(mLauncherContext1, mPackageContext1, "s2",
+                ActivityNotFoundException.class);
+    }
+
+    public void testPinnedShortcut_sameLauncher() {
+
+        // Let it publish a shortcut.
+        testStartSingle();
+
+        setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+        // then pin it.
+        runWithCaller(mLauncherContext1, () -> {
+            getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
+                    list("s1"), getUserHandle());
+        });
+
+        // Then remove it.
+        runWithCaller(mPackageContext1, () -> {
+            getManager().removeAllDynamicShortcuts();
+        });
+
+        // Should still be launchable.
+        assertShortcutStarts(mLauncherContext1, mPackageContext1, "s1");
+    }
+
+    public void testPinnedShortcut_differentLauncher() {
+
+        // Let it publish a shortcut.
+        testStartSingle();
+
+        setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+        // then pin it.
+        runWithCaller(mLauncherContext1, () -> {
+            getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
+                    list("s1"), getUserHandle());
+        });
+
+        // L2 is not the default, so can't launch it.
+        assertShortcutCantStart(mLauncherContext2, mPackageContext1, "s2",
+                SecurityException.class);
+
+        // Then change the launcher
+        setDefaultLauncher(getInstrumentation(), mLauncherContext2);
+
+        // L2 can now launch it.
+        assertShortcutStarts(mLauncherContext2, mPackageContext1, "s1");
+
+        // Then remove it.
+        runWithCaller(mPackageContext1, () -> {
+            getManager().removeAllDynamicShortcuts();
+        });
+
+        // L2 didn't pin it, so can't launch.
+        assertShortcutCantStart(mLauncherContext2, mPackageContext1, "s2",
+                ActivityNotFoundException.class);
+
+        // But launcher 1 can still launch it too, because it's pinned by this launcher.
+        assertShortcutStarts(mLauncherContext1, mPackageContext1, "s1");
+    }
+
+    public void testStartSingleWithOptions() {
+        testStartSingle();
+
+        List<Intent> launched = launchShortcutAndGetIntents(mLauncherContext1, mPackageContext1,
+                "s1", 1, new Rect(1, 1, 2, 2), new Bundle());
+
+        Intent i = launched.get(0);
+        assertEquals(1, i.getSourceBounds().left);
+        assertEquals(2, i.getSourceBounds().bottom);
+
+    }
+
+    public void testStartMultipleWithOptions() {
+        testStartMultiple();
+
+        List<Intent> launched = launchShortcutAndGetIntents(mLauncherContext1, mPackageContext1,
+                "s1", 3, new Rect(1, 1, 2, 2), new Bundle());
+
+        Intent i = launched.get(2);
+        assertEquals(1, i.getSourceBounds().left);
+        assertEquals(2, i.getSourceBounds().bottom);
+
+        i = launched.get(1);
+        assertEquals(null, i.getSourceBounds());
+
+        i = launched.get(0);
+        assertEquals(null, i.getSourceBounds());
+    }
+
+    public void testNonExistent() {
+        setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+        Intent i = new Intent(Intent.ACTION_MAIN)
+                .setComponent(new ComponentName("abc", "def"));
+
+        runWithCaller(mPackageContext1, () -> {
+            assertTrue(getManager().addDynamicShortcuts(list(
+                    makeShortcutBuilder("s1").setShortLabel("abc")
+                            .setIntent(i).build()
+            )));
+        });
+
+        assertExpectException(
+                ActivityNotFoundException.class, "Shortcut could not be started", () -> {
+            launchShortcutAndGetIntents(mLauncherContext1, mPackageContext1,
+                    "s1", 1);
+        });
+    }
+
+    /**
+     * Un-exported activities in other packages can't be started.
+     */
+    public void testUnExported() {
+        setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+        Intent i = new Intent(Intent.ACTION_MAIN)
+                .setComponent(new ComponentName(
+                        "android.content.pm.cts.shortcutmanager.packages.package4",
+                        "android.content.pm.cts.shortcutmanager.packages.Launcher"));
+
+        runWithCaller(mPackageContext1, () -> {
+            assertTrue(getManager().addDynamicShortcuts(list(
+                    makeShortcutBuilder("s1").setShortLabel("abc")
+                            .setIntent(i).build()
+            )));
+        });
+
+        assertExpectException(
+                ActivityNotFoundException.class, "Shortcut could not be started", () -> {
+                    launchShortcutAndGetIntents(mLauncherContext1, mPackageContext1,
+                            "s1", 1);
+                });
+    }
+}
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..3eb42cf
--- /dev/null
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerThrottlingTest.java
@@ -0,0 +1,190 @@
+/*
+ * 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.cts.shortcutmanager.common.Constants.INLINE_REPLY_REMOTE_INPUT_CAPTION;
+
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.resetThrottling;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.retryUntil;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.runCommandForNoOutput;
+
+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.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.Suppress;
+import android.view.KeyEvent;
+
+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 static final int UI_TIMEOUT = 5000;
+
+    private static final String TARGET_PACKAGE =
+            "android.content.pm.cts.shortcutmanager.throttling";
+
+    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(
+                    TARGET_PACKAGE + "/.ShortcutManagerThrottlingTestReceiver"));
+            getTestContext().sendBroadcast(i);
+
+            // Wait for the response.
+            retryUntil(() -> ret.get() != null, "Didn't receive result broadcast",
+                    120); // Wait much longer
+
+            if (ret.get().getExtras().getBoolean("success")) {
+                return;
+            }
+            fail(ret.get().getExtras().getString("error"));
+        } finally {
+            getTestContext().unregisterReceiver(r);
+        }
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        resetThrottling(getInstrumentation());
+
+        UiDevice.getInstance(getInstrumentation()).pressHome();
+
+        runCommandForNoOutput(getInstrumentation(), "am force-stop " + TARGET_PACKAGE);
+    }
+
+    public void testSetDynamicShortcuts() throws InterruptedException {
+        callTest(Constants.TEST_SET_DYNAMIC_SHORTCUTS);
+    }
+
+    public void testAddDynamicShortcuts() throws InterruptedException {
+        callTest(Constants.TEST_ADD_DYNAMIC_SHORTCUTS);
+    }
+
+    public void testUpdateShortcuts() throws InterruptedException {
+        callTest(Constants.TEST_UPDATE_SHORTCUTS);
+    }
+
+    public void testBgServiceThrottled() throws InterruptedException {
+        callTest(Constants.TEST_BG_SERVICE_THROTTLED);
+    }
+
+    public void testActivityUnthrottled() throws InterruptedException {
+        callTest(Constants.TEST_ACTIVITY_UNTHROTTLED);
+    }
+
+    public void testFgServiceUnthrottled() throws InterruptedException {
+        callTest(Constants.TEST_FG_SERVICE_UNTHROTTLED);
+    }
+
+    /**
+     * Flakey and may not work on OEM devices, so disabled.
+     */
+    @Suppress
+    public void testInlineReply() throws Exception {
+        clearNotifications();
+
+        callTest(Constants.TEST_INLINE_REPLY_SHOW);
+
+        performInlineReply();
+
+        callTest(Constants.TEST_INLINE_REPLY_CHECK);
+    }
+
+    private void clearNotifications() throws InterruptedException {
+        final UiDevice ud = UiDevice.getInstance(getInstrumentation());
+
+        // Open the notification shade.
+        ud.openNotification();
+
+        // Press "clear all", if found.
+        final UiObject2 clearAll = ud.wait(Until.findObject(By.text("CLEAR ALL")), UI_TIMEOUT);
+
+        // Just skip if not found.
+        if (clearAll != null) {
+            clearAll.clear();
+            ud.wait(Until.gone(By.text("CLEAR ALL")), UI_TIMEOUT);
+            Thread.sleep(1000);
+        }
+        // Close the notification.
+        ud.pressHome();
+        Thread.sleep(1000);
+    }
+
+    private void performInlineReply() throws InterruptedException {
+        final UiDevice ud = UiDevice.getInstance(getInstrumentation());
+
+        // Open the notification shade.
+        Thread.sleep(1000);
+        ud.openNotification();
+
+        // Find the inline reply part.
+        ud.wait(Until.findObject(By.text(INLINE_REPLY_REMOTE_INPUT_CAPTION)), UI_TIMEOUT).click();
+
+        Thread.sleep(1000);
+
+        // Type something.
+        ud.pressKeyCode(KeyEvent.KEYCODE_A);
+        ud.pressKeyCode(KeyEvent.KEYCODE_B);
+        ud.pressKeyCode(KeyEvent.KEYCODE_C);
+        ud.pressEnter();
+
+        Thread.sleep(1000);
+        ud.pressHome();
+
+        ud.wait(Until.gone(By.text(INLINE_REPLY_REMOTE_INPUT_CAPTION)), UI_TIMEOUT);
+
+        Thread.sleep(1000);
+    }
+}
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..aa0ad82
--- /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 = 60 * 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..4af2d7c
--- /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-minus-junit4 \
+    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..fcbc167
--- /dev/null
+++ b/tests/tests/shortcutmanager/throttling/AndroidManifest.xml
@@ -0,0 +1,42 @@
+<?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">
+
+    <!-- BG services will stop working on O, so set target to 25. -->
+    <uses-sdk android:minSdkVersion="25" android:targetSdkVersion="25" />
+
+    <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" />
+
+        <receiver android:name=".InlineReply" />
+    </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/InlineReply.java b/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/InlineReply.java
new file mode 100644
index 0000000..b1c21a6
--- /dev/null
+++ b/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/InlineReply.java
@@ -0,0 +1,56 @@
+/*
+ * 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 android.content.pm.cts.shortcutmanager.common.Constants.INLINE_REPLY_REMOTE_INPUT_CAPTION;
+import static android.content.pm.cts.shortcutmanager.common.Constants.INLINE_REPLY_TITLE;
+
+import android.app.Notification;
+import android.app.Notification.Action;
+import android.app.Notification.Builder;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+
+public class InlineReply extends BroadcastReceiver {
+    private static final String EXTRA_REPLY_ACTION = "reply";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        context.getSystemService(NotificationManager.class).cancelAll();
+    }
+
+    public static void showNotificationWithInlineReply(Context context) {
+        final PendingIntent receiverIntent =
+                PendingIntent.getBroadcast(context, 0,
+                        new Intent().setComponent(new ComponentName(context, InlineReply.class)),
+                        PendingIntent.FLAG_UPDATE_CURRENT);
+        final RemoteInput ri = new RemoteInput.Builder("result").setLabel("Remote input").build();
+
+        final Notification.Builder nb = new Builder(context)
+                .setContentText("Test")
+                .setContentTitle(INLINE_REPLY_TITLE)
+                .setSmallIcon(android.R.drawable.ic_popup_sync)
+                .addAction(new Action.Builder(0, INLINE_REPLY_REMOTE_INPUT_CAPTION, receiverIntent)
+                        .addRemoteInput(ri)
+                        .build());
+        context.getSystemService(NotificationManager.class).notify(0, nb.build());
+    }
+}
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..6edf7a8
--- /dev/null
+++ b/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/ShortcutManagerThrottlingTestReceiver.java
@@ -0,0 +1,169 @@
+/*
+ * 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 junit.framework.Assert.assertTrue;
+
+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;
+
+                case Constants.TEST_INLINE_REPLY_SHOW:
+                    testInlineReplyShow(context, replyAction);
+                    break;
+                case Constants.TEST_INLINE_REPLY_CHECK:
+                    testInlineReplyCheck(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);
+        });
+    }
+
+    public void testInlineReplyShow(Context context, String replyAction) {
+        ReplyUtil.runTestAndReply(context, replyAction, () -> {
+            // First make sure the self is throttled.
+            ThrottledTests.ensureThrottled(context);
+
+            InlineReply.showNotificationWithInlineReply(context);
+        });
+    }
+
+    public void testInlineReplyCheck(Context context, String replyAction) {
+        ReplyUtil.runTestAndReply(context, replyAction, () -> {
+            // Throttling should be reset, can make calls now.
+            assertTrue(context.getSystemService(ShortcutManager.class).setDynamicShortcuts(list()));
+            assertTrue(context.getSystemService(ShortcutManager.class).addDynamicShortcuts(list()));
+            assertTrue(context.getSystemService(ShortcutManager.class).updateShortcuts(list()));
+
+            // Make sure it's not considered to be in the FG -> so eventually the caller should be
+            // throttled.
+            ThrottledTests.ensureThrottled(context);
+        });
+    }
+}
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..94a854f 100644
--- a/tests/tests/telecom/src/android/telecom/cts/ConferenceTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/ConferenceTest.java
@@ -53,7 +53,8 @@
     private Call mCall1, mCall2;
     private MockConnection mConnection1, mConnection2;
     MockInCallService mInCallService;
-    MockConference mConferenceObject;
+    Conference mConferenceObject;
+    MockConference mConferenceVerficationObject;
 
     @Override
     protected void setUp() throws Exception {
@@ -61,8 +62,9 @@
         if (mShouldTestTelecom) {
             addOutgoingCalls();
             addConferenceCall(mCall1, mCall2);
-            // Use vanilla conference object so that the CTS coverage tool detects the useage.
-            mConferenceObject = verifyConferenceForOutgoingCall();
+            mConferenceVerficationObject = verifyConferenceForOutgoingCall();
+            // Use vanilla conference object so that the CTS coverage tool detects the usage.
+            mConferenceObject = mConferenceVerficationObject;
             verifyConferenceObject(mConferenceObject, mConnection1, mConnection2);
         }
     }
@@ -222,6 +224,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);
+        mConferenceVerficationObject.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/OutgoingCallTest.java b/tests/tests/telecom/src/android/telecom/cts/OutgoingCallTest.java
index fa19751..38a6709 100644
--- a/tests/tests/telecom/src/android/telecom/cts/OutgoingCallTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/OutgoingCallTest.java
@@ -72,6 +72,10 @@
         extras.putBoolean(TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, false);
         placeAndVerifyCall(extras);
         verifyConnectionForOutgoingCall();
+        if (mInCallCallbacks.getService().getCallAudioState().getSupportedRouteMask() ==
+                CallAudioState.ROUTE_SPEAKER) {
+            return; // Do not verify if the only available route is speaker.
+        }
         assertNotAudioRoute(mInCallCallbacks.getService(), CallAudioState.ROUTE_SPEAKER);
     }
 
@@ -84,6 +88,10 @@
 
         placeAndVerifyCall();
         verifyConnectionForOutgoingCall();
+        if (mInCallCallbacks.getService().getCallAudioState().getSupportedRouteMask() ==
+                CallAudioState.ROUTE_SPEAKER) {
+            return; // Do not verify if the only available route is speaker.
+        }
         assertNotAudioRoute(mInCallCallbacks.getService(), CallAudioState.ROUTE_SPEAKER);
     }
 }
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/AndroidManifest.xml b/tests/tests/view/AndroidManifest.xml
index 526e85f..ba1f3d2 100644
--- a/tests/tests/view/AndroidManifest.xml
+++ b/tests/tests/view/AndroidManifest.xml
@@ -19,8 +19,6 @@
     package="android.view.cts">
 
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-
     <application android:label="Android TestCase"
                 android:icon="@drawable/size_48x48"
                 android:maxRecents="1"
@@ -241,17 +239,8 @@
             </intent-filter>
         </activity>
 
-        <activity android:name="android.view.cts.DragDropActivity"
-                  android:label="DragDropActivity">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
-            </intent-filter>
-        </activity>
-
         <activity android:name="android.view.cts.surfacevalidator.CapturedActivity"
-            android:screenOrientation="portrait"
-            android:theme="@style/WhiteBackgroundTheme">
+                  android:theme="@style/WhiteBackgroundTheme">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
diff --git a/tests/tests/view/src/android/view/cts/MockView.java b/tests/tests/view/src/android/view/cts/MockView.java
index c5e4ab4..6b2cc18 100644
--- a/tests/tests/view/src/android/view/cts/MockView.java
+++ b/tests/tests/view/src/android/view/cts/MockView.java
@@ -71,10 +71,6 @@
     private boolean mCalledOnKeyPreIme = false;
     private boolean mCalledOnResolvePointerIcon = false;
     private boolean mCalledOnVisibilityAggregated = false;
-    private boolean mCalledDispatchStartTemporaryDetach = false;
-    private boolean mCalledDispatchFinishTemporaryDetach = false;
-    private boolean mCalledOnStartTemporaryDetach = false;
-    private boolean mCalledOnFinishTemporaryDetach = false;
 
     private int mOldWidth = -1;
     private int mOldHeight = -1;
@@ -638,46 +634,6 @@
         return mLastAggregatedVisibility;
     }
 
-    @Override
-    public void dispatchStartTemporaryDetach() {
-        super.dispatchStartTemporaryDetach();
-        mCalledDispatchStartTemporaryDetach = true;
-    }
-
-    @Override
-    public void dispatchFinishTemporaryDetach() {
-        super.dispatchFinishTemporaryDetach();
-        mCalledDispatchFinishTemporaryDetach = true;
-    }
-
-    @Override
-    public void onStartTemporaryDetach() {
-        super.onStartTemporaryDetach();
-        mCalledOnStartTemporaryDetach = true;
-    }
-
-    @Override
-    public void onFinishTemporaryDetach() {
-        super.onFinishTemporaryDetach();
-        mCalledOnFinishTemporaryDetach = true;
-    }
-
-    public boolean hasCalledDispatchStartTemporaryDetach() {
-        return mCalledDispatchStartTemporaryDetach;
-    }
-
-    public boolean hasCalledDispatchFinishTemporaryDetach() {
-        return mCalledDispatchFinishTemporaryDetach;
-    }
-
-    public boolean hasCalledOnStartTemporaryDetach() {
-        return mCalledOnStartTemporaryDetach;
-    }
-
-    public boolean hasCalledOnFinishTemporaryDetach() {
-        return mCalledOnFinishTemporaryDetach;
-    }
-
     public void reset() {
         mCalledOnCreateContextMenu = false;
 
@@ -719,10 +675,6 @@
         mCalledOnResolvePointerIcon = false;
         mCalledOnVisibilityAggregated = false;
         mCalledOnVisibilityAggregated = false;
-        mCalledDispatchStartTemporaryDetach = false;
-        mCalledDispatchFinishTemporaryDetach = false;
-        mCalledOnStartTemporaryDetach = false;
-        mCalledOnFinishTemporaryDetach = false;
 
         mOldWidth = -1;
         mOldHeight = -1;
diff --git a/tests/tests/view/src/android/view/cts/SurfaceViewSyncTests.java b/tests/tests/view/src/android/view/cts/SurfaceViewSyncTests.java
index e400864..876c178 100644
--- a/tests/tests/view/src/android/view/cts/SurfaceViewSyncTests.java
+++ b/tests/tests/view/src/android/view/cts/SurfaceViewSyncTests.java
@@ -19,12 +19,10 @@
 import android.animation.PropertyValuesHolder;
 import android.animation.ValueAnimator;
 import android.annotation.SuppressLint;
-import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
 import android.media.MediaPlayer;
-import android.os.Environment;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
@@ -34,7 +32,6 @@
 import android.support.test.uiautomator.UiSelector;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
-import android.util.SparseArray;
 import android.view.Gravity;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
@@ -47,19 +44,11 @@
 import android.view.cts.surfacevalidator.ViewFactory;
 import android.widget.FrameLayout;
 
-import libcore.io.IoUtils;
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.TestName;
 import org.junit.runner.RunWith;
 
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.concurrent.TimeUnit;
-
 import static org.junit.Assert.*;
 
 @RunWith(AndroidJUnit4.class)
@@ -67,22 +56,16 @@
 @SuppressLint("RtlHardcoded")
 public class SurfaceViewSyncTests {
     private static final String TAG = "SurfaceViewSyncTests";
-    private static final int PERMISSION_DIALOG_WAIT_MS = 1000;
+    private static final int PERMISSION_DIALOG_WAIT_MS = 500;
 
-    /**
-     * Want to be especially sure we don't leave up the permission dialog, so try and dismiss both
-     * before and after test.
-     */
     @Before
-    @After
     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)) {
-            boolean success = acceptButton.click();
-            Log.d(TAG, "found permission dialog, click attempt success = " + success);
+            assertTrue(acceptButton.click());
         }
     }
 
@@ -97,9 +80,6 @@
     @Rule
     public ActivityTestRule mActivityRule = new ActivityTestRule<>(CapturedActivity.class);
 
-    @Rule
-    public TestName mName = new TestName();
-
     static ValueAnimator makeInfinite(ValueAnimator a) {
         a.setRepeatMode(ObjectAnimator.REVERSE);
         a.setRepeatCount(ObjectAnimator.INFINITE);
@@ -185,76 +165,13 @@
     };
 
     ///////////////////////////////////////////////////////////////////////////
-    // Bad frame capture
-    ///////////////////////////////////////////////////////////////////////////
-
-    private void saveFailureCaptures(SparseArray<Bitmap> failFrames) {
-        if (failFrames.size() == 0) return;
-
-        String directoryName = Environment.getExternalStorageDirectory()
-                + "/" + getClass().getSimpleName()
-                + "/" + mName.getMethodName();
-        File testDirectory = new File(directoryName);
-        if (testDirectory.exists()) {
-            String[] children = testDirectory.list();
-            if (children == null) {
-                return;
-            }
-            for (String file : children) {
-                new File(testDirectory, file).delete();
-            }
-        } else {
-            testDirectory.mkdirs();
-        }
-
-        for (int i = 0; i < failFrames.size(); i++) {
-            int frameNr = failFrames.keyAt(i);
-            Bitmap bitmap = failFrames.valueAt(i);
-
-            String bitmapName =  "frame_" + frameNr + ".png";
-            Log.d(TAG, "Saving file : " + bitmapName + " in directory : " + directoryName);
-
-            File file = new File(directoryName, bitmapName);
-            FileOutputStream fileStream = null;
-            try {
-                fileStream = new FileOutputStream(file);
-                bitmap.compress(Bitmap.CompressFormat.PNG, 85, fileStream);
-                fileStream.flush();
-            } catch (IOException e) {
-                e.printStackTrace();
-            } finally {
-                IoUtils.closeQuietly(fileStream);
-            }
-        }
-    }
-
-
-    ///////////////////////////////////////////////////////////////////////////
     // Tests
     ///////////////////////////////////////////////////////////////////////////
 
-    public void verifyTest(AnimationTestCase testCase) throws InterruptedException {
-        CapturedActivity.TestResult result = getActivity().runTest(testCase);
-        saveFailureCaptures(result.failures);
-
-        float failRatio = 1.0f * result.failFrames / (result.failFrames + result.passFrames);
-        assertTrue("Error: " + failRatio + " fail ratio - extremely high, is activity obstructed?",
-                failRatio < 0.95f);
-        assertTrue("Error: " + result.failFrames
-                + " incorrect frames observed - incorrect positioning",
-                result.failFrames == 0);
-        float framesPerSecond = 1.0f * result.passFrames
-                / TimeUnit.MILLISECONDS.toSeconds(CapturedActivity.CAPTURE_DURATION_MS);
-        assertTrue("Error, only " + result.passFrames
-                + " frames observed, virtual display only capturing at "
-                + framesPerSecond + " frames per second",
-                result.passFrames > 100);
-    }
-
     /** Draws a moving 10x10 black rectangle, validates 100 pixels of black are seen each frame */
     @Test
-    public void testSmallRect() throws InterruptedException {
-        verifyTest(new AnimationTestCase(
+    public void testSmallRect() {
+        CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
                 context -> new View(context) {
                     // draw a single pixel
                     final Paint sBlackPaint = new Paint();
@@ -273,8 +190,9 @@
                 },
                 new FrameLayout.LayoutParams(100, 100, Gravity.LEFT | Gravity.TOP),
                 view -> makeInfinite(ObjectAnimator.ofInt(view, "offset", 10, 30)),
-                (blackishPixelCount, width, height) ->
-                        blackishPixelCount >= 90 && blackishPixelCount <= 110));
+                (blackishPixelCount, width, height) -> blackishPixelCount == 100));
+        assertTrue(result.passFrames > 100);
+        assertTrue(result.failFrames == 0);
     }
 
     /**
@@ -282,45 +200,53 @@
      * approximate to avoid rounding brittleness.
      */
     @Test
-    public void testEmptySurfaceView() throws InterruptedException {
-        verifyTest(new AnimationTestCase(
+    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() throws InterruptedException {
-        verifyTest(new AnimationTestCase(
+    public void testSurfaceViewSmallScale() {
+        CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
                 sGreenSurfaceViewFactory,
                 new FrameLayout.LayoutParams(320, 240, Gravity.LEFT | Gravity.TOP),
                 sSmallScaleAnimationFactory,
                 (blackishPixelCount, width, height) -> blackishPixelCount == 0));
+        assertTrue(result.passFrames > 100);
+        assertTrue(result.failFrames == 0);
     }
 
     @Test
-    public void testSurfaceViewBigScale() throws InterruptedException {
-        verifyTest(new AnimationTestCase(
+    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() throws InterruptedException {
-        verifyTest(new AnimationTestCase(
+    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() throws InterruptedException {
-        verifyTest(new AnimationTestCase(
+    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,
@@ -328,11 +254,13 @@
                         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() throws InterruptedException {
-        verifyTest(new AnimationTestCase(
+    public void testVideoSurfaceViewEdgeCoverage() {
+        CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
                 sVideoViewFactory,
                 new FrameLayout.LayoutParams(640, 480, Gravity.CENTER),
                 view -> {
@@ -346,11 +274,13 @@
                             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() throws InterruptedException {
-        verifyTest(new AnimationTestCase(
+    public void testVideoSurfaceViewCornerCoverage() {
+        CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
                 sVideoViewFactory,
                 new FrameLayout.LayoutParams(640, 480, Gravity.CENTER),
                 view -> {
@@ -364,5 +294,7 @@
                             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/ViewTest.java b/tests/tests/view/src/android/view/cts/ViewTest.java
index 7d11c68..19e267d 100644
--- a/tests/tests/view/src/android/view/cts/ViewTest.java
+++ b/tests/tests/view/src/android/view/cts/ViewTest.java
@@ -94,6 +94,7 @@
 import java.util.Collections;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * Test {@link View}.
@@ -3622,13 +3623,100 @@
     }
 
     public void testOnStartAndFinishTemporaryDetach() throws Throwable {
-        final MockView view = (MockView) mActivity.findViewById(R.id.mock_view);
+        final AtomicBoolean exitedDispatchStartTemporaryDetach = new AtomicBoolean(false);
+        final AtomicBoolean exitedDispatchFinishTemporaryDetach = new AtomicBoolean(false);
+
+        final View view = new View(mActivity) {
+            private boolean mEnteredDispatchStartTemporaryDetach = false;
+            private boolean mExitedDispatchStartTemporaryDetach = false;
+            private boolean mEnteredDispatchFinishTemporaryDetach = false;
+            private boolean mExitedDispatchFinishTemporaryDetach = false;
+
+            private boolean mCalledOnStartTemporaryDetach = false;
+            private boolean mCalledOnFinishTemporaryDetach = false;
+
+            @Override
+            public void dispatchStartTemporaryDetach() {
+                assertFalse(mEnteredDispatchStartTemporaryDetach);
+                assertFalse(mExitedDispatchStartTemporaryDetach);
+                assertFalse(mEnteredDispatchFinishTemporaryDetach);
+                assertFalse(mExitedDispatchFinishTemporaryDetach);
+                assertFalse(mCalledOnStartTemporaryDetach);
+                assertFalse(mCalledOnFinishTemporaryDetach);
+                mEnteredDispatchStartTemporaryDetach = true;
+
+                assertFalse(isTemporarilyDetached());
+
+                super.dispatchStartTemporaryDetach();
+
+                assertTrue(isTemporarilyDetached());
+
+                assertTrue(mEnteredDispatchStartTemporaryDetach);
+                assertFalse(mExitedDispatchStartTemporaryDetach);
+                assertFalse(mEnteredDispatchFinishTemporaryDetach);
+                assertFalse(mExitedDispatchFinishTemporaryDetach);
+                assertTrue(mCalledOnStartTemporaryDetach);
+                assertFalse(mCalledOnFinishTemporaryDetach);
+                mExitedDispatchStartTemporaryDetach = true;
+                exitedDispatchStartTemporaryDetach.set(true);
+            }
+
+            @Override
+            public void dispatchFinishTemporaryDetach() {
+                assertTrue(mEnteredDispatchStartTemporaryDetach);
+                assertTrue(mExitedDispatchStartTemporaryDetach);
+                assertFalse(mEnteredDispatchFinishTemporaryDetach);
+                assertFalse(mExitedDispatchFinishTemporaryDetach);
+                assertTrue(mCalledOnStartTemporaryDetach);
+                assertFalse(mCalledOnFinishTemporaryDetach);
+                mEnteredDispatchFinishTemporaryDetach = true;
+
+                assertTrue(isTemporarilyDetached());
+
+                super.dispatchFinishTemporaryDetach();
+
+                assertFalse(isTemporarilyDetached());
+
+                assertTrue(mEnteredDispatchStartTemporaryDetach);
+                assertTrue(mExitedDispatchStartTemporaryDetach);
+                assertTrue(mEnteredDispatchFinishTemporaryDetach);
+                assertFalse(mExitedDispatchFinishTemporaryDetach);
+                assertTrue(mCalledOnStartTemporaryDetach);
+                assertTrue(mCalledOnFinishTemporaryDetach);
+                mExitedDispatchFinishTemporaryDetach = true;
+                exitedDispatchFinishTemporaryDetach.set(true);
+            }
+
+            @Override
+            public void onStartTemporaryDetach() {
+                assertTrue(mEnteredDispatchStartTemporaryDetach);
+                assertFalse(mExitedDispatchStartTemporaryDetach);
+                assertFalse(mEnteredDispatchFinishTemporaryDetach);
+                assertFalse(mExitedDispatchFinishTemporaryDetach);
+                assertFalse(mCalledOnStartTemporaryDetach);
+                assertFalse(mCalledOnFinishTemporaryDetach);
+
+                assertTrue(isTemporarilyDetached());
+
+                mCalledOnStartTemporaryDetach = true;
+            }
+
+            @Override
+            public void onFinishTemporaryDetach() {
+                assertTrue(mEnteredDispatchStartTemporaryDetach);
+                assertTrue(mExitedDispatchStartTemporaryDetach);
+                assertTrue(mEnteredDispatchFinishTemporaryDetach);
+                assertFalse(mExitedDispatchFinishTemporaryDetach);
+                assertTrue(mCalledOnStartTemporaryDetach);
+                assertFalse(mCalledOnFinishTemporaryDetach);
+
+                assertFalse(isTemporarilyDetached());
+
+                mCalledOnFinishTemporaryDetach = true;
+            }
+        };
 
         assertFalse(view.isTemporarilyDetached());
-        assertFalse(view.hasCalledDispatchStartTemporaryDetach());
-        assertFalse(view.hasCalledDispatchFinishTemporaryDetach());
-        assertFalse(view.hasCalledOnStartTemporaryDetach());
-        assertFalse(view.hasCalledOnFinishTemporaryDetach());
 
         runTestOnUiThread(new Runnable() {
             @Override
@@ -3639,10 +3727,8 @@
         getInstrumentation().waitForIdleSync();
 
         assertTrue(view.isTemporarilyDetached());
-        assertTrue(view.hasCalledDispatchStartTemporaryDetach());
-        assertFalse(view.hasCalledDispatchFinishTemporaryDetach());
-        assertTrue(view.hasCalledOnStartTemporaryDetach());
-        assertFalse(view.hasCalledOnFinishTemporaryDetach());
+        assertTrue(exitedDispatchStartTemporaryDetach.get());
+        assertFalse(exitedDispatchFinishTemporaryDetach.get());
 
         runTestOnUiThread(new Runnable() {
             @Override
@@ -3653,10 +3739,8 @@
         getInstrumentation().waitForIdleSync();
 
         assertFalse(view.isTemporarilyDetached());
-        assertTrue(view.hasCalledDispatchStartTemporaryDetach());
-        assertTrue(view.hasCalledDispatchFinishTemporaryDetach());
-        assertTrue(view.hasCalledOnStartTemporaryDetach());
-        assertTrue(view.hasCalledOnFinishTemporaryDetach());
+        assertTrue(exitedDispatchStartTemporaryDetach.get());
+        assertTrue(exitedDispatchFinishTemporaryDetach.get());
     }
 
     public void testKeyPreIme() throws Throwable {
diff --git a/tests/tests/view/src/android/view/cts/surfacevalidator/CapturedActivity.java b/tests/tests/view/src/android/view/cts/surfacevalidator/CapturedActivity.java
index 82d113d..6a23e02 100644
--- a/tests/tests/view/src/android/view/cts/surfacevalidator/CapturedActivity.java
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/CapturedActivity.java
@@ -19,7 +19,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
-import android.graphics.Bitmap;
 import android.graphics.Point;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.VirtualDisplay;
@@ -31,27 +30,20 @@
 import android.os.Looper;
 import android.util.DisplayMetrics;
 import android.util.Log;
-import android.util.SparseArray;
 import android.view.Display;
 import android.view.View;
 import android.widget.FrameLayout;
 
 import android.view.cts.R;
 
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import static org.junit.Assert.*;
-
-
 public class CapturedActivity extends Activity {
     public static class TestResult {
         public int passFrames;
         public int failFrames;
-        public final SparseArray<Bitmap> failures = new SparseArray<>();
     }
 
     private static final String TAG = "CapturedActivity";
-    private static final long TIME_OUT_MS = 25000;
+    private static final long TIME_OUT_MS = 10000;
     private static final int PERMISSION_CODE = 1;
     private MediaProjectionManager mProjectionManager;
     private MediaProjection mMediaProjection;
@@ -60,38 +52,30 @@
     private SurfacePixelValidator mSurfacePixelValidator;
     private final Object mLock = new Object();
 
-    public static final long CAPTURE_DURATION_MS = 10000;
-
-    private static final long START_CAPTURE_DELAY_MS = 4000;
-    private static final long END_CAPTURE_DELAY_MS = START_CAPTURE_DELAY_MS + CAPTURE_DURATION_MS;
-    private static final long END_DELAY_MS = END_CAPTURE_DELAY_MS + 1000;
+    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;
-    private CountDownLatch mCountDownLatch;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        mOnWatch = getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
-        if (mOnWatch) {
-            // Don't try and set up test/capture infrastructure - they're not supported
-            return;
-        }
-
         getWindow().getDecorView().setSystemUiVisibility(
                 View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN);
 
         mProjectionManager =
                 (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
 
-        mCountDownLatch = new CountDownLatch(1);
         startActivityForResult(mProjectionManager.createScreenCaptureIntent(), PERMISSION_CODE);
 
         mMediaPlayer = MediaPlayer.create(this, R.raw.colors_video);
         mMediaPlayer.setLooping(true);
+
+        mOnWatch = getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
     }
 
     /**
@@ -113,23 +97,17 @@
 
     @Override
     public void onActivityResult(int requestCode, int resultCode, Intent data) {
-        if (mOnWatch) return;
-        getWindow().getDecorView().setSystemUiVisibility(
-                View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN);
-
         if (requestCode != PERMISSION_CODE) {
             throw new IllegalStateException("Unknown request code: " + requestCode);
         }
         if (resultCode != RESULT_OK) {
             throw new IllegalStateException("User denied screen sharing permission");
         }
-        Log.d(TAG, "onActivityResult");
         mMediaProjection = mProjectionManager.getMediaProjection(resultCode, data);
         mMediaProjection.registerCallback(new MediaProjectionCallback(), null);
-        mCountDownLatch.countDown();
     }
 
-    public TestResult runTest(AnimationTestCase animationTestCase) throws InterruptedException {
+    public TestResult runTest(AnimationTestCase animationTestCase) {
         TestResult testResult = new TestResult();
         if (mOnWatch) {
             /**
@@ -144,9 +122,6 @@
             return testResult;
         }
 
-        assertTrue("Can't initialize mediaProjection",
-                mCountDownLatch.await(TIME_OUT_MS, TimeUnit.MILLISECONDS));
-
         mHandler.post(() -> {
             Log.d(TAG, "Setting up test case");
 
@@ -168,7 +143,6 @@
             display.getRealSize(size);
             display.getMetrics(metrics);
 
-
             mSurfacePixelValidator = new SurfacePixelValidator(CapturedActivity.this,
                     size, animationTestCase.getChecker());
             Log.d("MediaProjection", "Size is " + size.toString());
@@ -198,7 +172,11 @@
         }, END_DELAY_MS);
 
         synchronized (mLock) {
-            mLock.wait(TIME_OUT_MS);
+            try {
+                mLock.wait(TIME_OUT_MS);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
         }
         Log.d(TAG, "Test finished, passFrames " + testResult.passFrames
                 + ", failFrames " + testResult.failFrames);
diff --git a/tests/tests/view/src/android/view/cts/surfacevalidator/PixelCounter.rs b/tests/tests/view/src/android/view/cts/surfacevalidator/PixelCounter.rs
index f58b9cb..55bc251 100644
--- a/tests/tests/view/src/android/view/cts/surfacevalidator/PixelCounter.rs
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/PixelCounter.rs
@@ -15,18 +15,22 @@
  */
 #pragma version(1)
 #pragma rs java_package_name(android.view.cts.surfacevalidator)
-#pragma rs reduce(countBlackishPixels) accumulator(countBlackishPixelsAccum) combiner(countBlackishPixelsCombiner)
 
+int WIDTH;
 uchar THRESHOLD;
 
-static void countBlackishPixelsAccum(int *accum, uchar4 pixel){
-    if (pixel.r < THRESHOLD
-            && pixel.g < THRESHOLD
-            && pixel.b < THRESHOLD) {
-        *accum += 1;
-    }
-}
+rs_allocation image;
 
-static void countBlackishPixelsCombiner(int *accum, const int *other){
-    *accum += *other;
+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
index 5a30b77..c9bff1d 100644
--- a/tests/tests/view/src/android/view/cts/surfacevalidator/SurfacePixelValidator.java
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/SurfacePixelValidator.java
@@ -16,7 +16,6 @@
 package android.view.cts.surfacevalidator;
 
 import android.content.Context;
-import android.graphics.Bitmap;
 import android.graphics.Point;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -26,13 +25,11 @@
 import android.renderscript.RenderScript;
 import android.renderscript.Type;
 import android.util.Log;
-import android.util.SparseArray;
 import android.view.Surface;
 import android.view.cts.surfacevalidator.PixelChecker;
 
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
-import java.util.ArrayList;
 
 public class SurfacePixelValidator {
     private static final String TAG = "SurfacePixelValidator";
@@ -46,8 +43,6 @@
     // If no channel is greater than this value, pixel will be considered 'blackish'.
     private static final short PIXEL_CHANNEL_THRESHOLD = 4;
 
-    private static final int MAX_CAPTURED_FAILURES = 5;
-
     private final int mWidth;
     private final int mHeight;
 
@@ -59,13 +54,14 @@
     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 SparseArray<Bitmap> mFirstFailures = new SparseArray<>(MAX_CAPTURED_FAILURES);
 
     private Runnable mConsumeRunnable = new Runnable() {
         int numSkipped = 0;
@@ -73,10 +69,15 @@
         public void run() {
             Trace.beginSection("consume buffer");
             mInPixelsAllocation.ioReceive();
+            mScript.set_image(mInPixelsAllocation);
             Trace.endSection();
 
-            Trace.beginSection("compare and sum");
-            int blackishPixelCount = mScript.reduce_countBlackishPixels(mInPixelsAllocation).get();
+            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);
@@ -84,6 +85,7 @@
                 if (numSkipped < NUM_FIRST_FRAMES_SKIPPED) {
                     numSkipped++;
                 } else {
+
                     if (success) {
                         mResultSuccessFrames++;
                     } else {
@@ -91,15 +93,6 @@
                         int totalFramesSeen = mResultSuccessFrames + mResultFailureFrames;
                         Log.d(TAG, "Failure (pixel count = " + blackishPixelCount
                                 + ") occurred on frame " + totalFramesSeen);
-
-                        if (mFirstFailures.size() < MAX_CAPTURED_FAILURES) {
-                            Log.d(TAG, "Capturing bitmap #" + mFirstFailures.size());
-                            // error, worth looking at...
-                            Bitmap capture = Bitmap.createBitmap(mWidth, mHeight,
-                                    Bitmap.Config.ARGB_8888);
-                            mInPixelsAllocation.copyTo(capture);
-                            mFirstFailures.put(totalFramesSeen, capture);
-                        }
                     }
                 }
             }
@@ -120,6 +113,9 @@
         mScript = new ScriptC_PixelCounter(mRS);
 
         mInPixelsAllocation = createBufferQueueAllocation();
+        mInRowsAllocation = createInputRowIndexAllocation();
+        mOutRowsAllocation = createOutputRowAllocation();
+        mScript.set_WIDTH(mWidth);
         mScript.set_THRESHOLD(PIXEL_CHANNEL_THRESHOLD);
 
         mInPixelsAllocation.setOnBufferAvailableListener(
@@ -130,10 +126,41 @@
         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.RGBA_8888(mRS)
-                /*Element.U32(mRS)*/, mWidth, mHeight),
+                Element.U8_4(mRS), mWidth, mHeight),
                 Allocation.USAGE_SCRIPT | Allocation.USAGE_IO_INPUT,
                 1)[0];
     }
@@ -150,10 +177,6 @@
             // Caller should only call this
             testResult.failFrames = mResultFailureFrames;
             testResult.passFrames = mResultSuccessFrames;
-
-            for (int i = 0; i < mFirstFailures.size(); i++) {
-                testResult.failures.put(mFirstFailures.keyAt(i), mFirstFailures.valueAt(i));
-            }
         }
         mWorkerThread.quitSafely();
     }
diff --git a/tests/tests/view/src/android/view/inputmethod/cts/BaseInputConnectionTest.java b/tests/tests/view/src/android/view/inputmethod/cts/BaseInputConnectionTest.java
index ee61446..ba239af 100644
--- a/tests/tests/view/src/android/view/inputmethod/cts/BaseInputConnectionTest.java
+++ b/tests/tests/view/src/android/view/inputmethod/cts/BaseInputConnectionTest.java
@@ -17,8 +17,10 @@
 package android.view.inputmethod.cts;
 
 import android.app.Instrumentation;
+import android.content.ClipDescription;
 import android.content.Context;
 import android.cts.util.PollingCheck;
+import android.net.Uri;
 import android.os.Bundle;
 import android.test.ActivityInstrumentationTestCase2;
 import android.text.Editable;
@@ -34,6 +36,7 @@
 import android.view.inputmethod.BaseInputConnection;
 import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.InputContentInfo;
 import android.view.inputmethod.InputMethodManager;
 import android.view.inputmethod.cts.util.InputConnectionTestUtils;
 import android.widget.EditText;
@@ -487,4 +490,32 @@
         testDeleteSurroundingTextInCodePointsMain("01[><]456789", 0, 1, "01[><]56789");
         testDeleteSurroundingTextInCodePointsMain("01[><]456789", 1, 1, "0[><]56789");
     }
+
+    public void testCloseConnection() {
+        final CharSequence source = "0123456789";
+        mConnection.commitText(source, source.length());
+        final Editable text = mConnection.getEditable();
+        BaseInputConnection.setComposingSpans(text, 2, 5);
+        assertEquals(2, BaseInputConnection.getComposingSpanStart(text));
+        assertEquals(5, BaseInputConnection.getComposingSpanEnd(text));
+
+        // BaseInputConnection#closeConnection() must clear the on-going composition.
+        mConnection.closeConnection();
+        assertEquals(-1, BaseInputConnection.getComposingSpanStart(text));
+        assertEquals(-1, BaseInputConnection.getComposingSpanEnd(text));
+    }
+
+    public void testGetHandler() {
+        // BaseInputConnection must not implement getHandler().
+        assertNull(mConnection.getHandler());
+    }
+
+    public void testCommitContent() {
+        final InputContentInfo inputContentInfo = new InputContentInfo(
+                Uri.parse("content://com.example/path"),
+                new ClipDescription("sample content", new String[]{"image/png"}),
+                Uri.parse("https://example.com"));
+        // The default implementation should do nothing and just return false.
+        assertFalse(mConnection.commitContent(inputContentInfo, 0 /* flags */, null /* opts */));
+    }
 }
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..8bcb611 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 {
 
@@ -92,8 +95,15 @@
         wrapper.closeConnection();
         assertTrue(inputConnection.isCloseConnectionCalled);
         assertFalse(inputConnection.isGetHandlerCalled);
-        assertNull(inputConnection.getHandler());
+        assertNull(wrapper.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"));
+        wrapper.commitContent(inputContentInfo, 0 /* flags */, null /* opts */);
+        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..d8eb897
--- /dev/null
+++ b/tests/tests/view/src/android/view/inputmethod/cts/InputContentInfoTest.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.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());
+        assertEquals(0, info.describeContents());
+
+        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());
+        assertEquals(info.describeContents(), targetInfo.describeContents());
+    }
+
+    public void testOptionalConstructorParam() {
+        InputContentInfo info = new InputContentInfo(
+                Uri.parse("content://com.example/path"),
+                new ClipDescription("sample content", new String[]{"image/png"}));
+
+        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());
+        assertNull(info.getLinkUri());
+        assertEquals(0, info.describeContents());
+    }
+
+    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);
+        }
+    }
+
+    public void testRequestAndReleasePermission() {
+        InputContentInfo info = new InputContentInfo(
+                Uri.parse("content://com.example/path"),
+                new ClipDescription("sample content", new String[]{"image/png"}),
+                Uri.parse("https://example.com"));
+
+        // Here we only assert that {request, release}Permission() do not crash, because ensuring
+        // the entire functionality of these methods requires end-to-end IME test environment, which
+        // we do not have yet in CTS.
+        // Note it is actually intentional that calling these methods here has no effect.  Those
+        // methods would have effect only after the object is passed from the IME process to the
+        // application process.
+        // TODO: Create an end-to-end CTS test for this functionality.
+        info.requestPermission();
+        info.releasePermission();
+        info.requestPermission();
+        info.releasePermission();
+    }
+
+}
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-api-coverage/src/com/android/cts/apicoverage/ApiClass.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiClass.java
index 2d24931..9eeb907 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiClass.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiClass.java
@@ -95,27 +95,28 @@
     }
 
     /** Look for a matching constructor and mark it as covered */
-    public void markConstructorCovered(List<String> parameterTypes) {
+    public void markConstructorCovered(List<String> parameterTypes, String coveredbyApk) {
         if (mSuperClass != null) {
             // Mark matching constructors in the superclass
-            mSuperClass.markConstructorCovered(parameterTypes);
+            mSuperClass.markConstructorCovered(parameterTypes, coveredbyApk);
         }
         ApiConstructor apiConstructor = getConstructor(parameterTypes);
         if (apiConstructor != null) {
-            apiConstructor.setCovered(true);
+            apiConstructor.setCovered(coveredbyApk);
         }
 
     }
 
     /** Look for a matching method and if found and mark it as covered */
-    public void markMethodCovered(String name, List<String> parameterTypes, String returnType) {
+    public void markMethodCovered(
+            String name, List<String> parameterTypes, String returnType, String coveredbyApk) {
         if (mSuperClass != null) {
             // Mark matching methods in the super class
-            mSuperClass.markMethodCovered(name, parameterTypes, returnType);
+            mSuperClass.markMethodCovered(name, parameterTypes, returnType, coveredbyApk);
         }
         ApiMethod apiMethod = getMethod(name, parameterTypes, returnType);
         if (apiMethod != null) {
-            apiMethod.setCovered(true);
+            apiMethod.setCovered(coveredbyApk);
         }
     }
 
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiConstructor.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiConstructor.java
index 8d721e6..a6fbf12 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiConstructor.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiConstructor.java
@@ -18,7 +18,9 @@
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /** Representation of a constructor in the API with parameters (arguments). */
 class ApiConstructor implements Comparable<ApiConstructor> {
@@ -29,7 +31,8 @@
 
     private final boolean mDeprecated;
 
-    private boolean mIsCovered;
+    // A list of test APKs (aka CTS modules) that use this method.
+    private final Set<String> mCoveredWith = new HashSet<>();
 
     ApiConstructor(String name, List<String> parameterTypes, boolean deprecated) {
         mName = name;
@@ -55,10 +58,17 @@
     }
 
     public boolean isCovered() {
-        return mIsCovered;
+        return !mCoveredWith.isEmpty();
     }
 
-    public void setCovered(boolean covered) {
-        mIsCovered = covered;
+    public void setCovered(String coveredWithModule) {
+        if (coveredWithModule.endsWith(".apk")) {
+            coveredWithModule = coveredWithModule.substring(0, coveredWithModule.length() - 4);
+        }
+        mCoveredWith.add(coveredWithModule);
     }
-}
\ No newline at end of file
+
+    public Set<String> getCoveredWith() {
+        return mCoveredWith;
+    }
+}
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiMethod.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiMethod.java
index 582c2b6..56c225d 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiMethod.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiMethod.java
@@ -18,7 +18,10 @@
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
+
 
 /** Representation of a method in the API with parameters (arguments) and a return value. */
 class ApiMethod implements Comparable<ApiMethod> {
@@ -39,7 +42,8 @@
 
     private final boolean mAbstractMethod;
 
-    private boolean mIsCovered;
+    // A list of test APKs (aka CTS modules) that use this method.
+    private final Set<String> mCoveredWith = new HashSet<>();
 
     ApiMethod(
             String name,
@@ -82,7 +86,7 @@
     }
 
     public boolean isCovered() {
-        return mIsCovered;
+        return !mCoveredWith.isEmpty();
     }
 
     public String getVisibility() { return mVisibility; }
@@ -93,7 +97,13 @@
 
     public boolean isFinalMethod() { return mFinalMethod; }
 
-    public void setCovered(boolean covered) {
-        mIsCovered = covered;
+    public Set<String> getCoveredWith() { return mCoveredWith; }
+
+    public void setCovered(String coveredWithModule) {
+        if (coveredWithModule.endsWith(".apk")) {
+            coveredWithModule = coveredWithModule.substring(0, coveredWithModule.length() - 4);
+        }
+
+        mCoveredWith.add(coveredWithModule);
     }
-}
\ No newline at end of file
+}
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/CtsApiCoverage.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/CtsApiCoverage.java
index caea3d6..6b69a57 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/CtsApiCoverage.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/CtsApiCoverage.java
@@ -187,7 +187,8 @@
     private static void addApiCoverage(ApiCoverage apiCoverage, File testApk, String dexdeps)
             throws SAXException, IOException {
         XMLReader xmlReader = XMLReaderFactory.createXMLReader();
-        DexDepsXmlHandler dexDepsXmlHandler = new DexDepsXmlHandler(apiCoverage);
+        String testApkName = testApk.getName();
+        DexDepsXmlHandler dexDepsXmlHandler = new DexDepsXmlHandler(apiCoverage, testApkName);
         xmlReader.setContentHandler(dexDepsXmlHandler);
 
         String apkPath = testApk.getPath();
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/DexDepsXmlHandler.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/DexDepsXmlHandler.java
index 3df532e..b728654 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/DexDepsXmlHandler.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/DexDepsXmlHandler.java
@@ -31,6 +31,8 @@
 
     private final ApiCoverage mPackageMap;
 
+    private final String mTestApkName;
+
     private String mCurrentPackageName;
 
     private String mCurrentClassName;
@@ -41,8 +43,9 @@
 
     private List<String> mCurrentParameterTypes = new ArrayList<String>();
 
-    DexDepsXmlHandler(ApiCoverage packageMap) {
+    DexDepsXmlHandler(ApiCoverage packageMap, String testApkName) {
         this.mPackageMap = packageMap;
+        this.mTestApkName = testApkName;
     }
 
     @Override
@@ -73,7 +76,7 @@
             if (apiPackage != null) {
                 ApiClass apiClass = apiPackage.getClass(mCurrentClassName);
                 if (apiClass != null) {
-                    apiClass.markConstructorCovered(mCurrentParameterTypes);
+                    apiClass.markConstructorCovered(mCurrentParameterTypes, mTestApkName);
                 }
             }
         }  else if ("method".equalsIgnoreCase(localName)) {
@@ -82,9 +85,10 @@
                 ApiClass apiClass = apiPackage.getClass(mCurrentClassName);
                 if (apiClass != null) {
                     apiClass.markMethodCovered(
-                            mCurrentMethodName, mCurrentParameterTypes, mCurrentMethodReturnType);
+                            mCurrentMethodName, mCurrentParameterTypes, mCurrentMethodReturnType,
+                                    mTestApkName);
                 }
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/XmlReport.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/XmlReport.java
index 4310d20..2ba16cf 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/XmlReport.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/XmlReport.java
@@ -24,6 +24,7 @@
 import java.util.Collections;
 import java.util.Date;
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * Class that outputs an XML report of the {@link ApiCoverage} collected. It can be viewed in
@@ -83,9 +84,13 @@
                                 + "\">");
 
                         for (ApiConstructor constructor : apiClass.getConstructors()) {
+                            String coveredWithList =
+                                    constructor.getCoveredWith().stream().collect(Collectors.joining(","));
                             out.println("<constructor name=\"" + constructor.getName()
                                     + "\" deprecated=\"" + constructor.isDeprecated()
-                                    + "\" covered=\"" + constructor.isCovered() + "\">");
+                                    + "\" covered=\"" + constructor.isCovered()
+                                    + "\" with=\"" + coveredWithList
+                                    + "\">");
                             if (constructor.isDeprecated()) {
                                 if (constructor.isCovered()) {
                                     totalCoveredMethods -= 1;
@@ -100,6 +105,8 @@
                         }
 
                         for (ApiMethod method : apiClass.getMethods()) {
+                            String coveredWithList =
+                                    method.getCoveredWith().stream().collect(Collectors.joining(","));
                             out.println("<method name=\"" + method.getName()
                                     + "\" returnType=\"" + method.getReturnType()
                                     + "\" deprecated=\"" + method.isDeprecated()
@@ -107,7 +114,9 @@
                                     + "\" final=\"" + method.isFinalMethod()
                                     + "\" visibility=\"" + method.getVisibility()
                                     + "\" abstract=\"" + method.isAbstractMethod()
-                                    + "\" covered=\"" + method.isCovered() + "\">");
+                                    + "\" covered=\"" + method.isCovered()
+                                    + "\" with=\"" + coveredWithList
+                                    + "\">");
                             if (method.isDeprecated()) {
                                 if (method.isCovered()) {
                                     totalCoveredMethods -= 1;
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/Android.mk b/tools/cts-tradefed/Android.mk
index 28102e8..bb4b934 100644
--- a/tools/cts-tradefed/Android.mk
+++ b/tools/cts-tradefed/Android.mk
@@ -25,7 +25,7 @@
 LOCAL_SUITE_TARGET_ARCH := $(TARGET_ARCH)
 LOCAL_SUITE_NAME := CTS
 LOCAL_SUITE_FULLNAME := "Compatibility Test Suite"
-LOCAL_SUITE_VERSION := 7.0_r201706s
+LOCAL_SUITE_VERSION := 7.1_r1
 
 LOCAL_MODULE := cts-tradefed
 
diff --git a/tools/cts-tradefed/res/config/cts-known-failures.xml b/tools/cts-tradefed/res/config/cts-known-failures.xml
index abcc0ec..4b59d31 100644
--- a/tools/cts-tradefed/res/config/cts-known-failures.xml
+++ b/tools/cts-tradefed/res/config/cts-known-failures.xml
@@ -27,9 +27,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/build/CtsBuildProvider.java b/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildProvider.java
index 2463f87..7cdbedb 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildProvider.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildProvider.java
@@ -48,7 +48,7 @@
     @Option(name="branch", description="build branch name to supply.")
     private String mBranch = null;
 
-    public static final String CTS_BUILD_VERSION = "7.0_r201701s";
+    public static final String CTS_BUILD_VERSION = "7.0_r0";
     public static final String CTS_PACKAGE = "com.android.cts.tradefed.testtype";
 
     /**